mirror of
https://github.com/colanode/colanode.git
synced 2025-12-14 18:57:46 +01:00
Add configurable storage backends (File, S3, GCS, Azure) (#225)
This commit is contained in:
13
.github/workflows/helm-chart-publish.yml
vendored
13
.github/workflows/helm-chart-publish.yml
vendored
@@ -2,10 +2,8 @@ name: Hosting - Publish Helm chart to static.colanode.com
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'hosting/kubernetes/chart/**'
|
||||
- '.github/workflows/helm-chart-publish.yml'
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -22,11 +20,18 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- uses: azure/setup-helm@v3
|
||||
with: { version: v3.14.3 }
|
||||
|
||||
- run: helm dependency update "$CHART_DIR"
|
||||
|
||||
- name: Update Chart.yaml appVersion
|
||||
run: |
|
||||
sed -i "s/^appVersion:.*/appVersion: '${{ env.VERSION }}'/" "$CHART_DIR/Chart.yaml"
|
||||
|
||||
- name: Configure AWS CLI
|
||||
run: aws configure set default.s3.addressing_style path
|
||||
|
||||
|
||||
11
README.md
11
README.md
@@ -58,7 +58,7 @@ If you prefer to host your own Colanode server, check out the [`hosting/`](hosti
|
||||
|
||||
- **Postgres** with the **pgvector** extension.
|
||||
- **Redis** (any Redis-compatible service will work, e.g., Valkey).
|
||||
- **S3-compatible storage** (supporting basic file operations: PUT, GET, DELETE).
|
||||
- **Storage backend** for user files. Colanode defaults to local filesystem storage, but you can switch to **S3-compatible**, **Google Cloud Storage**, or **Azure Blob Storage** backends by setting `STORAGE_TYPE`.
|
||||
- **Colanode server API**, provided as a Docker image.
|
||||
|
||||
All required environment variables for the Colanode server can be found in the [`hosting/docker/docker-compose.yaml`](hosting/docker/docker-compose.yaml) file or [`hosting/kubernetes/README.md`](hosting/kubernetes/README.md) for Kubernetes deployments.
|
||||
@@ -93,13 +93,20 @@ To run Colanode locally in development mode:
|
||||
npm run dev
|
||||
```
|
||||
|
||||
To spin up the local dependencies (Postgres, Redis, Minio & Mail server) with Docker Compose, run this from
|
||||
To spin up the local dependencies (Postgres, Redis, and Mail server) with Docker Compose—using filesystem storage
|
||||
by default—run this from
|
||||
the project root:
|
||||
|
||||
```bash
|
||||
docker compose -f hosting/docker/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
When you prefer an S3-compatible backend locally, enable the optional MinIO service with the `s3` profile:
|
||||
|
||||
```bash
|
||||
docker compose -f hosting/docker/docker-compose.yaml --profile s3 up -d
|
||||
```
|
||||
|
||||
The compose file includes a `server` service. When you want to run the API locally with `npm run dev`, comment
|
||||
out (or override) that service so only the supporting services are started.
|
||||
|
||||
|
||||
@@ -66,14 +66,43 @@ REDIS_URL=redis://:your_valkey_password@localhost:6379/0
|
||||
# REDIS_EVENTS_CHANNEL=events
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# S3 Storage Configuration (MinIO)
|
||||
# Storage Configuration
|
||||
# Supported types: 's3', 'file', 'gcs', 'azure'
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
STORAGE_S3_ENDPOINT=http://localhost:9000
|
||||
STORAGE_S3_ACCESS_KEY=minioadmin
|
||||
STORAGE_S3_SECRET_KEY=your_minio_password
|
||||
STORAGE_S3_BUCKET=colanode
|
||||
STORAGE_S3_REGION=us-east-1
|
||||
STORAGE_S3_FORCE_PATH_STYLE=true
|
||||
STORAGE_TYPE=file
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# S3 Storage Configuration (MinIO, AWS S3, or S3-compatible)
|
||||
# Required when STORAGE_TYPE=s3
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# STORAGE_S3_ENDPOINT=http://localhost:9000
|
||||
# STORAGE_S3_ACCESS_KEY=minioadmin
|
||||
# STORAGE_S3_SECRET_KEY=your_minio_password
|
||||
# STORAGE_S3_BUCKET=colanode
|
||||
# STORAGE_S3_REGION=us-east-1
|
||||
# STORAGE_S3_FORCE_PATH_STYLE=true
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# File Storage Configuration (Local Disk)
|
||||
# Required when STORAGE_TYPE=file
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
STORAGE_FILE_DIRECTORY=./colanode
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# Google Cloud Storage (GCS) Configuration
|
||||
# Required when STORAGE_TYPE=gcs
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# STORAGE_GCS_BUCKET=your-gcs-bucket-name
|
||||
# STORAGE_GCS_PROJECT_ID=your-gcp-project-id
|
||||
# STORAGE_GCS_CREDENTIALS=/path/to/service-account-key.json
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# Azure Blob Storage Configuration
|
||||
# Required when STORAGE_TYPE=azure
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# STORAGE_AZURE_ACCOUNT=your-storage-account-name
|
||||
# STORAGE_AZURE_ACCOUNT_KEY=your-storage-account-key
|
||||
# STORAGE_AZURE_CONTAINER_NAME=colanode
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# SMTP Configuration
|
||||
|
||||
@@ -32,12 +32,16 @@
|
||||
"@colanode/crdt": "*",
|
||||
"@fastify/cors": "^11.1.0",
|
||||
"@fastify/websocket": "^11.2.0",
|
||||
"@google-cloud/storage": "^7.15.0",
|
||||
"@langchain/core": "^0.3.78",
|
||||
"@langchain/google-genai": "^0.2.18",
|
||||
"@langchain/langgraph": "^0.4.9",
|
||||
"@langchain/openai": "^0.6.14",
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"@redis/client": "^5.8.3",
|
||||
"@tus/azure-store": "^2.0.0",
|
||||
"@tus/file-store": "^2.0.0",
|
||||
"@tus/gcs-store": "^2.0.0",
|
||||
"@tus/s3-store": "^2.0.1",
|
||||
"@tus/server": "^2.3.0",
|
||||
"bullmq": "^5.61.0",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import ky from 'ky';
|
||||
import sharp from 'sharp';
|
||||
@@ -14,12 +13,12 @@ import {
|
||||
} from '@colanode/core';
|
||||
import { database } from '@colanode/server/data/database';
|
||||
import { UpdateAccount } from '@colanode/server/data/schema';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import {
|
||||
buildLoginSuccessOutput,
|
||||
buildLoginVerifyOutput,
|
||||
} from '@colanode/server/lib/accounts';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
import { AccountAttributes } from '@colanode/server/types/accounts';
|
||||
|
||||
const GoogleUserInfoUrl = 'https://www.googleapis.com/oauth2/v1/userinfo';
|
||||
@@ -113,14 +112,7 @@ const uploadGooglePictureAsAvatar = async (
|
||||
.toBuffer();
|
||||
|
||||
const avatarId = generateId(IdType.Avatar);
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: `avatars/${avatarId}.jpeg`,
|
||||
Body: jpegBuffer,
|
||||
ContentType: 'image/jpeg',
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
await storage.upload(`avatars/${avatarId}.jpeg`, jpegBuffer, 'image/jpeg');
|
||||
|
||||
return avatarId;
|
||||
} catch {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import { ApiErrorCode } from '@colanode/core';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
export const avatarDownloadRoute: FastifyPluginCallbackZod = (
|
||||
instance,
|
||||
@@ -24,28 +20,10 @@ export const avatarDownloadRoute: FastifyPluginCallbackZod = (
|
||||
handler: async (request, reply) => {
|
||||
try {
|
||||
const avatarId = request.params.avatarId;
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: `avatars/${avatarId}.jpeg`,
|
||||
});
|
||||
const { stream } = await storage.download(`avatars/${avatarId}.jpeg`);
|
||||
|
||||
const avatarResponse = await s3Client.send(command);
|
||||
if (!avatarResponse.Body) {
|
||||
return reply.code(400).send({
|
||||
code: ApiErrorCode.AvatarNotFound,
|
||||
message: 'Avatar not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (avatarResponse.Body instanceof Readable) {
|
||||
reply.header('Content-Type', 'image/jpeg');
|
||||
return reply.send(avatarResponse.Body);
|
||||
}
|
||||
|
||||
return reply.code(400).send({
|
||||
code: ApiErrorCode.AvatarNotFound,
|
||||
message: 'Avatar not found',
|
||||
});
|
||||
reply.header('Content-Type', 'image/jpeg');
|
||||
return reply.send(stream);
|
||||
} catch {
|
||||
return reply.code(500).send({
|
||||
code: ApiErrorCode.AvatarDownloadFailed,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import sharp from 'sharp';
|
||||
|
||||
@@ -9,8 +8,7 @@ import {
|
||||
generateId,
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
const ALLOWED_MIME_TYPES = [
|
||||
'image/jpeg',
|
||||
@@ -70,14 +68,7 @@ export const avatarUploadRoute: FastifyPluginCallbackZod = (
|
||||
.toBuffer();
|
||||
|
||||
const avatarId = generateId(IdType.Avatar);
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: `avatars/${avatarId}.jpeg`,
|
||||
Body: jpegBuffer,
|
||||
ContentType: 'image/jpeg',
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
await storage.upload(`avatars/${avatarId}.jpeg`, jpegBuffer, 'image/jpeg');
|
||||
|
||||
return { success: true, id: avatarId };
|
||||
} catch {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
@@ -11,9 +8,8 @@ import {
|
||||
FileStatus,
|
||||
} from '@colanode/core';
|
||||
import { database } from '@colanode/server/data/database';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { fetchNodeTree, mapNode } from '@colanode/server/lib/nodes';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
export const fileDownloadRoute: FastifyPluginCallbackZod = (
|
||||
instance,
|
||||
@@ -84,28 +80,20 @@ export const fileDownloadRoute: FastifyPluginCallbackZod = (
|
||||
});
|
||||
}
|
||||
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: upload.path,
|
||||
});
|
||||
try {
|
||||
const { stream, contentType } = await storage.download(upload.path);
|
||||
|
||||
const fileResponse = await s3Client.send(command);
|
||||
if (!fileResponse.Body) {
|
||||
if (contentType) {
|
||||
reply.header('Content-Type', contentType);
|
||||
}
|
||||
|
||||
return reply.send(stream);
|
||||
} catch (error) {
|
||||
return reply.code(404).send({
|
||||
code: ApiErrorCode.FileNotFound,
|
||||
message: 'File not found.',
|
||||
});
|
||||
}
|
||||
|
||||
if (fileResponse.Body instanceof Readable) {
|
||||
reply.header('Content-Type', fileResponse.ContentType);
|
||||
return reply.send(fileResponse.Body);
|
||||
}
|
||||
|
||||
return reply.code(404).send({
|
||||
code: ApiErrorCode.FileNotFound,
|
||||
message: 'File not found.',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,34 +1,19 @@
|
||||
import { S3Store } from '@tus/s3-store';
|
||||
import { Server } from '@tus/server';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import {
|
||||
ApiErrorCode,
|
||||
FILE_UPLOAD_PART_SIZE,
|
||||
FileStatus,
|
||||
generateId,
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
import { ApiErrorCode, FileStatus, generateId, IdType } from '@colanode/core';
|
||||
import { database } from '@colanode/server/data/database';
|
||||
import { redis } from '@colanode/server/data/redis';
|
||||
import { s3Config } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { fetchCounter } from '@colanode/server/lib/counters';
|
||||
import { generateUrl } from '@colanode/server/lib/fastify';
|
||||
import { buildFilePath, deleteFile } from '@colanode/server/lib/files';
|
||||
import { buildFilePath } from '@colanode/server/lib/files';
|
||||
import { mapNode, updateNode } from '@colanode/server/lib/nodes';
|
||||
import { RedisKvStore } from '@colanode/server/lib/tus/redis-kv';
|
||||
import { RedisLocker } from '@colanode/server/lib/tus/redis-locker';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
import { RedisLocker } from '@colanode/server/lib/storage/tus/redis-locker';
|
||||
|
||||
const s3Store = new S3Store({
|
||||
partSize: FILE_UPLOAD_PART_SIZE,
|
||||
cache: new RedisKvStore(redis, config.redis.tus.kvPrefix),
|
||||
s3ClientConfig: {
|
||||
...s3Config,
|
||||
bucket: config.storage.bucket,
|
||||
},
|
||||
});
|
||||
const tusStore = storage.tusStore;
|
||||
|
||||
export const fileUploadTusRoute: FastifyPluginCallbackZod = (
|
||||
instance,
|
||||
@@ -102,7 +87,7 @@ export const fileUploadTusRoute: FastifyPluginCallbackZod = (
|
||||
|
||||
const tusServer = new Server({
|
||||
path: '/tus',
|
||||
datastore: s3Store,
|
||||
datastore: tusStore,
|
||||
locker: new RedisLocker(redis, config.redis.tus.lockPrefix),
|
||||
async onUploadCreate() {
|
||||
const upload = await database
|
||||
@@ -277,7 +262,7 @@ export const fileUploadTusRoute: FastifyPluginCallbackZod = (
|
||||
}
|
||||
|
||||
const tusInfoPath = `${path}.info`;
|
||||
await deleteFile(tusInfoPath);
|
||||
await storage.delete(tusInfoPath);
|
||||
|
||||
return {
|
||||
status_code: 200,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { FastifyPluginCallbackZod } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
@@ -10,11 +9,10 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
import { database } from '@colanode/server/data/database';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { fetchCounter } from '@colanode/server/lib/counters';
|
||||
import { buildFilePath } from '@colanode/server/lib/files';
|
||||
import { mapNode, updateNode } from '@colanode/server/lib/nodes';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
export const fileUploadRoute: FastifyPluginCallbackZod = (
|
||||
instance,
|
||||
@@ -150,16 +148,13 @@ export const fileUploadRoute: FastifyPluginCallbackZod = (
|
||||
const path = buildFilePath(workspaceId, fileId, file.attributes);
|
||||
|
||||
const stream = request.raw;
|
||||
const uploadCommand = new PutObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: path,
|
||||
Body: stream,
|
||||
ContentType: file.attributes.mimeType,
|
||||
ContentLength: file.attributes.size,
|
||||
});
|
||||
|
||||
try {
|
||||
await s3Client.send(uploadCommand);
|
||||
await storage.upload(
|
||||
path,
|
||||
stream,
|
||||
file.attributes.mimeType,
|
||||
BigInt(file.attributes.size)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return reply.code(500).send({
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3';
|
||||
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
|
||||
export const s3Config: S3ClientConfig = {
|
||||
endpoint: config.storage.endpoint,
|
||||
region: config.storage.region,
|
||||
credentials: {
|
||||
accessKeyId: config.storage.accessKey,
|
||||
secretAccessKey: config.storage.secretKey,
|
||||
},
|
||||
forcePathStyle: config.storage.forcePathStyle,
|
||||
};
|
||||
|
||||
export const s3Client = new S3Client(s3Config);
|
||||
@@ -4,8 +4,8 @@ import { CreateNodeTombstone } from '@colanode/server/data/schema';
|
||||
import { JobHandler } from '@colanode/server/jobs';
|
||||
import { updateDocument } from '@colanode/server/lib/documents';
|
||||
import { eventBus } from '@colanode/server/lib/event-bus';
|
||||
import { deleteFile } from '@colanode/server/lib/files';
|
||||
import { createLogger } from '@colanode/server/lib/logger';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
const BATCH_SIZE = 100;
|
||||
const logger = createLogger('server:job:clean-node-data');
|
||||
@@ -162,7 +162,7 @@ const cleanNodeFiles = async (nodeIds: string[]) => {
|
||||
|
||||
if (uploads.length > 0) {
|
||||
for (const upload of uploads) {
|
||||
await deleteFile(upload.path);
|
||||
await storage.delete(upload.path);
|
||||
}
|
||||
|
||||
await database
|
||||
|
||||
@@ -4,9 +4,9 @@ import { database } from '@colanode/server/data/database';
|
||||
import { redis } from '@colanode/server/data/redis';
|
||||
import { JobHandler } from '@colanode/server/jobs';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { deleteFile } from '@colanode/server/lib/files';
|
||||
import { createLogger } from '@colanode/server/lib/logger';
|
||||
import { RedisKvStore } from '@colanode/server/lib/tus/redis-kv';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
import { RedisKvStore } from '@colanode/server/lib/storage/tus/redis-kv';
|
||||
|
||||
const logger = createLogger('server:job:uploads-clean');
|
||||
|
||||
@@ -42,11 +42,11 @@ export const uploadsCleanHandler: JobHandler<UploadsCleanInput> = async () => {
|
||||
|
||||
const redisKv = new RedisKvStore(redis, config.redis.tus.kvPrefix);
|
||||
for (const upload of expiredUploads) {
|
||||
await deleteFile(upload.path);
|
||||
await storage.delete(upload.path);
|
||||
await redisKv.delete(upload.path);
|
||||
|
||||
const infoPath = `${upload.path}.info`;
|
||||
await deleteFile(infoPath);
|
||||
await storage.delete(infoPath);
|
||||
|
||||
await database
|
||||
.deleteFrom('uploads')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { database } from '@colanode/server/data/database';
|
||||
import { JobHandler } from '@colanode/server/jobs';
|
||||
import { deleteFile } from '@colanode/server/lib/files';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
import { createLogger } from '@colanode/server/lib/logger';
|
||||
|
||||
const BATCH_SIZE = 500;
|
||||
@@ -143,7 +143,7 @@ const deleteWorkspaceUploads = async (workspaceId: string) => {
|
||||
}
|
||||
|
||||
for (const upload of uploads) {
|
||||
await deleteFile(upload.path);
|
||||
await storage.delete(upload.path);
|
||||
}
|
||||
|
||||
const fileIds = uploads.map((upload) => upload.file_id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
export const storageConfigSchema = z.object({
|
||||
const s3StorageConfigSchema = z.object({
|
||||
type: z.literal('s3'),
|
||||
endpoint: z.string({ error: 'STORAGE_S3_ENDPOINT is required' }),
|
||||
accessKey: z.string({ error: 'STORAGE_S3_ACCESS_KEY is required' }),
|
||||
@@ -10,16 +10,80 @@ export const storageConfigSchema = z.object({
|
||||
forcePathStyle: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type StorageConfig = z.infer<typeof storageConfigSchema>;
|
||||
const fileStorageConfigSchema = z.object({
|
||||
type: z.literal('file'),
|
||||
directory: z.string({ error: 'STORAGE_FILE_DIRECTORY is required' }),
|
||||
});
|
||||
|
||||
export const readStorageConfigVariables = () => {
|
||||
return {
|
||||
type: 's3',
|
||||
endpoint: process.env.STORAGE_S3_ENDPOINT,
|
||||
accessKey: process.env.STORAGE_S3_ACCESS_KEY,
|
||||
secretKey: process.env.STORAGE_S3_SECRET_KEY,
|
||||
bucket: process.env.STORAGE_S3_BUCKET,
|
||||
region: process.env.STORAGE_S3_REGION,
|
||||
forcePathStyle: process.env.STORAGE_S3_FORCE_PATH_STYLE === 'true',
|
||||
};
|
||||
const gcsStorageConfigSchema = z.object({
|
||||
type: z.literal('gcs'),
|
||||
bucket: z.string({ error: 'STORAGE_GCS_BUCKET is required' }),
|
||||
projectId: z.string({ error: 'STORAGE_GCS_PROJECT_ID is required' }),
|
||||
credentials: z.string({ error: 'STORAGE_GCS_CREDENTIALS is required' }),
|
||||
});
|
||||
|
||||
const azureStorageConfigSchema = z.object({
|
||||
type: z.literal('azure'),
|
||||
account: z.string({ error: 'STORAGE_AZURE_ACCOUNT is required' }),
|
||||
accountKey: z.string({ error: 'STORAGE_AZURE_ACCOUNT_KEY is required' }),
|
||||
containerName: z.string({ error: 'STORAGE_AZURE_CONTAINER_NAME is required' }),
|
||||
});
|
||||
|
||||
export const storageConfigSchema = z.discriminatedUnion('type', [
|
||||
s3StorageConfigSchema,
|
||||
fileStorageConfigSchema,
|
||||
gcsStorageConfigSchema,
|
||||
azureStorageConfigSchema,
|
||||
]);
|
||||
|
||||
export type StorageConfig = z.infer<typeof storageConfigSchema>;
|
||||
export type S3StorageConfig = z.infer<typeof s3StorageConfigSchema>;
|
||||
export type FileStorageConfig = z.infer<typeof fileStorageConfigSchema>;
|
||||
export type GCSStorageConfig = z.infer<typeof gcsStorageConfigSchema>;
|
||||
export type AzureStorageConfig = z.infer<typeof azureStorageConfigSchema>;
|
||||
|
||||
export const readStorageConfigVariables = (): StorageConfig => {
|
||||
const storageType = process.env.STORAGE_TYPE || 's3';
|
||||
|
||||
switch (storageType) {
|
||||
case 's3':
|
||||
return {
|
||||
type: 's3',
|
||||
endpoint: process.env.STORAGE_S3_ENDPOINT,
|
||||
accessKey: process.env.STORAGE_S3_ACCESS_KEY,
|
||||
secretKey: process.env.STORAGE_S3_SECRET_KEY,
|
||||
bucket: process.env.STORAGE_S3_BUCKET,
|
||||
region: process.env.STORAGE_S3_REGION,
|
||||
forcePathStyle: process.env.STORAGE_S3_FORCE_PATH_STYLE === 'true',
|
||||
} as z.infer<typeof s3StorageConfigSchema>;
|
||||
case 'file':
|
||||
return {
|
||||
type: 'file',
|
||||
directory: process.env.STORAGE_FILE_DIRECTORY,
|
||||
} as z.infer<typeof fileStorageConfigSchema>;
|
||||
case 'gcs':
|
||||
return {
|
||||
type: 'gcs',
|
||||
bucket: process.env.STORAGE_GCS_BUCKET,
|
||||
projectId: process.env.STORAGE_GCS_PROJECT_ID,
|
||||
credentials: process.env.STORAGE_GCS_CREDENTIALS,
|
||||
} as z.infer<typeof gcsStorageConfigSchema>;
|
||||
case 'azure':
|
||||
return {
|
||||
type: 'azure',
|
||||
account: process.env.STORAGE_AZURE_ACCOUNT,
|
||||
accountKey: process.env.STORAGE_AZURE_ACCOUNT_KEY,
|
||||
containerName: process.env.STORAGE_AZURE_CONTAINER_NAME,
|
||||
} as z.infer<typeof azureStorageConfigSchema>;
|
||||
default:
|
||||
return {
|
||||
type: 's3',
|
||||
endpoint: process.env.STORAGE_S3_ENDPOINT,
|
||||
accessKey: process.env.STORAGE_S3_ACCESS_KEY,
|
||||
secretKey: process.env.STORAGE_S3_SECRET_KEY,
|
||||
bucket: process.env.STORAGE_S3_BUCKET,
|
||||
region: process.env.STORAGE_S3_REGION,
|
||||
forcePathStyle: process.env.STORAGE_S3_FORCE_PATH_STYLE === 'true',
|
||||
} as z.infer<typeof s3StorageConfigSchema>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||
|
||||
import { FileAttributes } from '@colanode/core';
|
||||
import { s3Client } from '@colanode/server/data/storage';
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
|
||||
export const buildFilePath = (
|
||||
workspaceId: string,
|
||||
@@ -11,12 +8,3 @@ export const buildFilePath = (
|
||||
) => {
|
||||
return `files/${workspaceId}/${fileId}_${fileAttributes.version}${fileAttributes.extension}`;
|
||||
};
|
||||
|
||||
export const deleteFile = async (path: string) => {
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: config.storage.bucket,
|
||||
Key: path,
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
};
|
||||
|
||||
@@ -30,8 +30,8 @@ import {
|
||||
checkCollaboratorChanges,
|
||||
} from '@colanode/server/lib/collaborations';
|
||||
import { eventBus } from '@colanode/server/lib/event-bus';
|
||||
import { deleteFile } from '@colanode/server/lib/files';
|
||||
import { createLogger } from '@colanode/server/lib/logger';
|
||||
import { storage } from '@colanode/server/lib/storage';
|
||||
import { jobService } from '@colanode/server/services/job-service';
|
||||
import {
|
||||
ConcurrentUpdateResult,
|
||||
@@ -711,7 +711,7 @@ export const deleteNodeFromMutation = async (
|
||||
.executeTakeFirst();
|
||||
|
||||
if (upload) {
|
||||
await deleteFile(upload.path);
|
||||
await storage.delete(upload.path);
|
||||
|
||||
await database
|
||||
.deleteFrom('uploads')
|
||||
|
||||
103
apps/server/src/lib/storage/azure.ts
Normal file
103
apps/server/src/lib/storage/azure.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { DataStore } from '@tus/server';
|
||||
import { AzureStore } from '@tus/azure-store';
|
||||
import {
|
||||
BlobServiceClient,
|
||||
StorageSharedKeyCredential,
|
||||
BlockBlobClient,
|
||||
} from '@azure/storage-blob';
|
||||
import type { AzureStorageConfig } from '@colanode/server/lib/config/storage';
|
||||
|
||||
import type { Storage } from './core';
|
||||
|
||||
export class AzureBlobStorage implements Storage {
|
||||
private readonly containerName: string;
|
||||
private readonly blobServiceClient: BlobServiceClient;
|
||||
private readonly config: AzureStorageConfig;
|
||||
public readonly tusStore: DataStore;
|
||||
|
||||
constructor(config: AzureStorageConfig) {
|
||||
this.config = { ...config };
|
||||
const sharedKeyCredential = new StorageSharedKeyCredential(
|
||||
this.config.account,
|
||||
this.config.accountKey
|
||||
);
|
||||
|
||||
this.blobServiceClient = new BlobServiceClient(
|
||||
`https://${this.config.account}.blob.core.windows.net`,
|
||||
sharedKeyCredential
|
||||
);
|
||||
this.containerName = this.config.containerName;
|
||||
|
||||
this.tusStore = new AzureStore({
|
||||
account: this.config.account,
|
||||
accountKey: this.config.accountKey,
|
||||
containerName: this.containerName,
|
||||
});
|
||||
}
|
||||
|
||||
private getBlockBlobClient(path: string): BlockBlobClient {
|
||||
const containerClient = this.blobServiceClient.getContainerClient(
|
||||
this.containerName
|
||||
);
|
||||
return containerClient.getBlockBlobClient(path);
|
||||
}
|
||||
|
||||
async download(
|
||||
path: string
|
||||
): Promise<{ stream: Readable; contentType?: string }> {
|
||||
const containerClient = this.blobServiceClient.getContainerClient(
|
||||
this.containerName
|
||||
);
|
||||
const blobClient = containerClient.getBlobClient(path);
|
||||
const downloadResponse = await blobClient.download();
|
||||
|
||||
if (!downloadResponse.readableStreamBody) {
|
||||
throw new Error('Failed to download blob: no readable stream body');
|
||||
}
|
||||
|
||||
return {
|
||||
stream: downloadResponse.readableStreamBody as Readable,
|
||||
contentType: downloadResponse.contentType,
|
||||
};
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<void> {
|
||||
const containerClient = this.blobServiceClient.getContainerClient(
|
||||
this.containerName
|
||||
);
|
||||
const blobClient = containerClient.getBlobClient(path);
|
||||
await blobClient.delete();
|
||||
}
|
||||
|
||||
async upload(
|
||||
path: string,
|
||||
data: Buffer | Readable,
|
||||
contentType: string,
|
||||
contentLength?: bigint
|
||||
): Promise<void> {
|
||||
const blockBlobClient = this.getBlockBlobClient(path);
|
||||
|
||||
if (data instanceof Buffer) {
|
||||
await blockBlobClient.upload(data, data.length, {
|
||||
blobHTTPHeaders: {
|
||||
blobContentType: contentType,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contentLength) {
|
||||
throw new Error(
|
||||
'Content length is required for stream uploads to Azure Blob Storage'
|
||||
);
|
||||
}
|
||||
|
||||
await blockBlobClient.uploadStream(data as Readable, undefined, undefined, {
|
||||
blobHTTPHeaders: {
|
||||
blobContentType: contentType,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
14
apps/server/src/lib/storage/core.ts
Normal file
14
apps/server/src/lib/storage/core.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Readable } from 'stream';
|
||||
import { DataStore } from '@tus/server';
|
||||
|
||||
export interface Storage {
|
||||
download(path: string): Promise<{ stream: Readable; contentType?: string }>;
|
||||
delete(path: string): Promise<void>;
|
||||
upload(
|
||||
path: string,
|
||||
data: Buffer | Readable,
|
||||
contentType: string,
|
||||
contentLength?: bigint
|
||||
): Promise<void>;
|
||||
readonly tusStore: DataStore;
|
||||
}
|
||||
58
apps/server/src/lib/storage/fs.ts
Normal file
58
apps/server/src/lib/storage/fs.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createReadStream, createWriteStream, promises as fs } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { DataStore } from '@tus/server';
|
||||
import { FileStore } from '@tus/file-store';
|
||||
import type { FileStorageConfig } from '@colanode/server/lib/config/storage';
|
||||
|
||||
import type { Storage } from './core';
|
||||
|
||||
export class FileSystemStorage implements Storage {
|
||||
private readonly directory: string;
|
||||
public readonly tusStore: DataStore;
|
||||
|
||||
constructor(config: FileStorageConfig) {
|
||||
this.directory = config.directory;
|
||||
this.tusStore = new FileStore({ directory: this.directory });
|
||||
}
|
||||
|
||||
async download(
|
||||
path: string
|
||||
): Promise<{ stream: Readable; contentType?: string }> {
|
||||
const fullPath = `${this.directory}/${path}`;
|
||||
const stream = createReadStream(fullPath);
|
||||
|
||||
return {
|
||||
stream,
|
||||
contentType: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<void> {
|
||||
const fullPath = `${this.directory}/${path}`;
|
||||
await fs.unlink(fullPath);
|
||||
}
|
||||
|
||||
async upload(
|
||||
path: string,
|
||||
data: Buffer | Readable,
|
||||
_contentType: string,
|
||||
_contentLength?: bigint
|
||||
): Promise<void> {
|
||||
const fullPath = `${this.directory}/${path}`;
|
||||
const dirPath = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
|
||||
if (data instanceof Buffer) {
|
||||
await fs.writeFile(fullPath, data);
|
||||
return;
|
||||
}
|
||||
|
||||
const writeStream = createWriteStream(fullPath);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
(data as Readable).pipe(writeStream);
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
69
apps/server/src/lib/storage/gcs.ts
Normal file
69
apps/server/src/lib/storage/gcs.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { Storage, Bucket, File } from '@google-cloud/storage';
|
||||
import { DataStore } from '@tus/server';
|
||||
import { GCSStore } from '@tus/gcs-store';
|
||||
import type { GCSStorageConfig } from '@colanode/server/lib/config/storage';
|
||||
|
||||
import type { Storage as StorageInterface } from './core';
|
||||
|
||||
export class GCSStorage implements StorageInterface {
|
||||
private readonly bucket: Bucket;
|
||||
public readonly tusStore: DataStore;
|
||||
|
||||
constructor(config: GCSStorageConfig) {
|
||||
const storage = new Storage({
|
||||
projectId: config.projectId,
|
||||
keyFilename: config.credentials,
|
||||
});
|
||||
|
||||
this.bucket = storage.bucket(config.bucket);
|
||||
this.tusStore = new GCSStore({ bucket: this.bucket });
|
||||
}
|
||||
|
||||
private getFile(path: string): File {
|
||||
return this.bucket.file(path);
|
||||
}
|
||||
|
||||
async download(
|
||||
path: string
|
||||
): Promise<{ stream: Readable; contentType?: string }> {
|
||||
const file = this.getFile(path);
|
||||
const [metadata] = await file.getMetadata();
|
||||
const stream = file.createReadStream();
|
||||
|
||||
return {
|
||||
stream,
|
||||
contentType: metadata.contentType,
|
||||
};
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<void> {
|
||||
const file = this.getFile(path);
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
async upload(
|
||||
path: string,
|
||||
data: Buffer | Readable,
|
||||
contentType: string,
|
||||
_contentLength?: bigint
|
||||
): Promise<void> {
|
||||
const file = this.getFile(path);
|
||||
|
||||
if (data instanceof Buffer) {
|
||||
await file.save(data, { contentType });
|
||||
return;
|
||||
}
|
||||
|
||||
const writeStream = file.createWriteStream({
|
||||
metadata: { contentType },
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
(data as Readable).pipe(writeStream);
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
30
apps/server/src/lib/storage/index.ts
Normal file
30
apps/server/src/lib/storage/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
|
||||
import type { StorageConfig } from '../config/storage';
|
||||
|
||||
import type { Storage } from './core';
|
||||
import { AzureBlobStorage } from './azure';
|
||||
import { FileSystemStorage } from './fs';
|
||||
import { GCSStorage } from './gcs';
|
||||
import { S3Storage } from './s3';
|
||||
|
||||
const buildStorage = (storageConfig: StorageConfig): Storage => {
|
||||
switch (storageConfig.type) {
|
||||
case 'file':
|
||||
return new FileSystemStorage(storageConfig);
|
||||
case 's3':
|
||||
return new S3Storage(storageConfig);
|
||||
case 'gcs':
|
||||
return new GCSStorage(storageConfig);
|
||||
case 'azure':
|
||||
return new AzureBlobStorage(storageConfig);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported storage type: ${(storageConfig as any).type}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const storage = buildStorage(config.storage);
|
||||
|
||||
export type { Storage } from './core';
|
||||
93
apps/server/src/lib/storage/s3.ts
Normal file
93
apps/server/src/lib/storage/s3.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
PutObjectCommand,
|
||||
S3Client,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { RedisClientType } from '@redis/client';
|
||||
import { FILE_UPLOAD_PART_SIZE } from '@colanode/core';
|
||||
import { DataStore } from '@tus/server';
|
||||
import { S3Store } from '@tus/s3-store';
|
||||
|
||||
import { config } from '@colanode/server/lib/config';
|
||||
import { RedisKvStore } from '@colanode/server/lib/storage/tus/redis-kv';
|
||||
import type { S3StorageConfig } from '@colanode/server/lib/config/storage';
|
||||
import { redis } from '@colanode/server/data/redis';
|
||||
import type { Storage } from './core';
|
||||
|
||||
export class S3Storage implements Storage {
|
||||
private readonly client: S3Client;
|
||||
private readonly bucket: string;
|
||||
private readonly s3Config: S3StorageConfig;
|
||||
public readonly tusStore: DataStore;
|
||||
|
||||
constructor(s3Config: S3StorageConfig) {
|
||||
this.s3Config = { ...s3Config };
|
||||
this.client = new S3Client({
|
||||
endpoint: this.s3Config.endpoint,
|
||||
region: this.s3Config.region,
|
||||
credentials: {
|
||||
accessKeyId: this.s3Config.accessKey,
|
||||
secretAccessKey: this.s3Config.secretKey,
|
||||
},
|
||||
forcePathStyle: this.s3Config.forcePathStyle,
|
||||
});
|
||||
|
||||
this.bucket = this.s3Config.bucket;
|
||||
|
||||
this.tusStore = new S3Store({
|
||||
partSize: FILE_UPLOAD_PART_SIZE,
|
||||
cache: new RedisKvStore(redis, config.redis.tus.kvPrefix),
|
||||
s3ClientConfig: {
|
||||
bucket: this.bucket,
|
||||
endpoint: this.s3Config.endpoint,
|
||||
region: this.s3Config.region,
|
||||
forcePathStyle: this.s3Config.forcePathStyle,
|
||||
credentials: {
|
||||
accessKeyId: this.s3Config.accessKey,
|
||||
secretAccessKey: this.s3Config.secretKey,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async download(
|
||||
path: string
|
||||
): Promise<{ stream: Readable; contentType?: string }> {
|
||||
const command = new GetObjectCommand({ Bucket: this.bucket, Key: path });
|
||||
const response = await this.client.send(command);
|
||||
|
||||
if (!response.Body || !(response.Body instanceof Readable)) {
|
||||
throw new Error('File not found or invalid response body');
|
||||
}
|
||||
|
||||
return {
|
||||
stream: response.Body,
|
||||
contentType: response.ContentType,
|
||||
};
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<void> {
|
||||
const command = new DeleteObjectCommand({ Bucket: this.bucket, Key: path });
|
||||
await this.client.send(command);
|
||||
}
|
||||
|
||||
async upload(
|
||||
path: string,
|
||||
data: Buffer | Readable,
|
||||
contentType: string,
|
||||
contentLength?: bigint
|
||||
): Promise<void> {
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: path,
|
||||
Body: data,
|
||||
ContentType: contentType,
|
||||
ContentLength: contentLength ? Number(contentLength) : undefined,
|
||||
});
|
||||
|
||||
await this.client.send(command);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,12 @@ services:
|
||||
networks:
|
||||
- colanode_network
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Optional MinIO Object Storage (Enable when using S3 storage)
|
||||
# ---------------------------------------------------------------
|
||||
minio:
|
||||
profiles:
|
||||
- s3
|
||||
image: minio/minio:RELEASE.2025-04-08T15-41-24Z
|
||||
container_name: colanode_minio
|
||||
restart: always
|
||||
@@ -77,7 +82,6 @@ services:
|
||||
depends_on:
|
||||
- postgres
|
||||
- valkey
|
||||
- minio
|
||||
# - smtp # Optional
|
||||
environment:
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
@@ -158,15 +162,20 @@ services:
|
||||
# REDIS_EVENTS_CHANNEL: 'events'
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# S3 configuration for files.
|
||||
# In the future we will support other storage providers.
|
||||
# Storage configuration
|
||||
# Supported storage types: 'file', 's3', 'gcs', 'azure'
|
||||
# By default we store files on the local filesystem.
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
STORAGE_S3_ENDPOINT: 'http://minio:9000'
|
||||
STORAGE_S3_ACCESS_KEY: 'minioadmin'
|
||||
STORAGE_S3_SECRET_KEY: 'your_minio_password'
|
||||
STORAGE_S3_BUCKET: 'colanode'
|
||||
STORAGE_S3_REGION: 'us-east-1'
|
||||
STORAGE_S3_FORCE_PATH_STYLE: 'true'
|
||||
STORAGE_TYPE: 'file'
|
||||
STORAGE_FILE_DIRECTORY: '/var/lib/colanode/storage'
|
||||
# To use the optional MinIO service (or any S3-compatible storage):
|
||||
# STORAGE_TYPE: 's3'
|
||||
# STORAGE_S3_ENDPOINT: 'http://minio:9000'
|
||||
# STORAGE_S3_ACCESS_KEY: 'minioadmin'
|
||||
# STORAGE_S3_SECRET_KEY: 'your_minio_password'
|
||||
# STORAGE_S3_BUCKET: 'colanode'
|
||||
# STORAGE_S3_REGION: 'us-east-1'
|
||||
# STORAGE_S3_FORCE_PATH_STYLE: 'true'
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# SMTP configuration
|
||||
@@ -196,6 +205,8 @@ services:
|
||||
|
||||
ports:
|
||||
- '3000:3000'
|
||||
volumes:
|
||||
- server_storage:/var/lib/colanode/storage
|
||||
networks:
|
||||
- colanode_network
|
||||
|
||||
@@ -212,6 +223,7 @@ volumes:
|
||||
postgres_data:
|
||||
valkey_data:
|
||||
minio_data:
|
||||
server_storage:
|
||||
|
||||
networks:
|
||||
colanode_network:
|
||||
|
||||
@@ -9,7 +9,8 @@ This chart deploys a complete Colanode instance with all required dependencies:
|
||||
- **Colanode Server**: The main application server
|
||||
- **PostgreSQL**: Database with pgvector extension for vector operations
|
||||
- **Redis/Valkey**: Message queue and caching
|
||||
- **MinIO**: S3-compatible object storage for files and avatars
|
||||
- **File Storage (default)**: Persistent volume for user files and avatars
|
||||
- **Optional Object Storage**: MinIO (S3-compatible), external S3, Google Cloud Storage, or Azure Blob Storage
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -62,11 +63,40 @@ helm install my-colanode ./hosting/kubernetes/chart \
|
||||
|
||||
### Dependencies
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| -------------------- | ---------------------------- | ------- |
|
||||
| `postgresql.enabled` | Enable PostgreSQL deployment | `true` |
|
||||
| `redis.enabled` | Enable Redis deployment | `true` |
|
||||
| `minio.enabled` | Enable MinIO deployment | `true` |
|
||||
| Parameter | Description | Default |
|
||||
| -------------------- | ----------------------------------------------------------------- | ------- |
|
||||
| `postgresql.enabled` | Enable PostgreSQL deployment | `true` |
|
||||
| `redis.enabled` | Enable Redis deployment | `true` |
|
||||
| `minio.enabled` | Enable bundled MinIO (only required for the in-cluster S3 option) | `false` |
|
||||
|
||||
### Storage Configuration
|
||||
|
||||
Set `colanode.storage.type` to choose where user files and avatars are stored:
|
||||
|
||||
- **File storage (default)** mounts a persistent volume at `/var/lib/colanode/storage`. Adjust `colanode.storage.file.persistence` to control the PVC size, storage class, or reference an existing claim.
|
||||
- **S3-compatible storage** (Amazon S3, MinIO, Cloudflare R2, etc.) requires `colanode.storage.type=s3`. Enable the bundled MinIO instance with `--set minio.enabled=true` or supply your provider endpoint, bucket, region, and credentials via `colanode.storage.s3.*`.
|
||||
- **Google Cloud Storage** needs a service-account JSON key. Create a secret:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic gcs-credentials \
|
||||
--from-file=service-account.json=/path/to/key.json
|
||||
```
|
||||
|
||||
Then configure:
|
||||
|
||||
```yaml
|
||||
colanode:
|
||||
storage:
|
||||
type: gcs
|
||||
gcs:
|
||||
bucket: your-bucket
|
||||
projectId: your-project
|
||||
credentialsSecret:
|
||||
name: gcs-credentials
|
||||
key: service-account.json
|
||||
```
|
||||
|
||||
- **Azure Blob Storage** is available with `colanode.storage.type=azure`. Provide the storage `account`, `containerName`, and the account key via `colanode.storage.azure.accountKey` (inline value or an existing secret).
|
||||
|
||||
## Important Notes
|
||||
|
||||
@@ -78,9 +108,10 @@ The chart includes `global.security.allowInsecureImages: true` because we use a
|
||||
|
||||
By default, the chart configures persistent storage for:
|
||||
|
||||
- Colanode file storage (PVC): 20Gi
|
||||
- PostgreSQL: 8Gi
|
||||
- Redis: 8Gi
|
||||
- MinIO: 10Gi
|
||||
- MinIO: 10Gi (only when `minio.enabled=true`)
|
||||
|
||||
Adjust these values based on your requirements.
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ apiVersion: v2
|
||||
name: colanode
|
||||
description: A Helm chart for Colanode - open-source & local-first collaboration workspace
|
||||
type: application
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
# appVersion is auto-updated by the release workflow
|
||||
appVersion: '1.0.0'
|
||||
|
||||
dependencies:
|
||||
|
||||
@@ -82,6 +82,13 @@ Return the MinIO hostname
|
||||
{{- printf "%s-minio" .Release.Name -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Return the default PVC name used for file storage
|
||||
*/}}
|
||||
{{- define "colanode.storagePvcName" -}}
|
||||
{{- printf "%s-storage" (include "colanode.fullname" .) -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Helper to get value from secret key reference or direct value
|
||||
Usage: {{ include "colanode.getValueOrSecret" (dict "key" "theKey" "value" .Values.path.to.value) }}
|
||||
@@ -210,34 +217,81 @@ Colanode Server Environment Variables
|
||||
value: {{ .Values.colanode.config.REDIS_EVENTS_CHANNEL | quote }}
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# S3 Configuration for Storage
|
||||
# Storage Configuration
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
- name: STORAGE_TYPE
|
||||
value: {{ default "file" .Values.colanode.storage.type | quote }}
|
||||
{{- $storageType := default "file" .Values.colanode.storage.type }}
|
||||
{{- if eq $storageType "file" }}
|
||||
- name: STORAGE_FILE_DIRECTORY
|
||||
value: {{ required "colanode.storage.file.directory must be set when STORAGE_TYPE is file" .Values.colanode.storage.file.directory | quote }}
|
||||
{{- end }}
|
||||
{{- if eq $storageType "s3" }}
|
||||
{{- $s3 := .Values.colanode.storage.s3 }}
|
||||
{{- $endpoint := $s3.endpoint }}
|
||||
{{- if and (not $endpoint) (not .Values.minio.enabled) }}
|
||||
{{- fail "colanode.storage.s3.endpoint must be provided when MinIO is disabled" }}
|
||||
{{- end }}
|
||||
- name: STORAGE_S3_ENDPOINT
|
||||
value: "http://{{ include "colanode.minio.hostname" . }}:9000"
|
||||
- name: STORAGE_S3_ACCESS_KEY
|
||||
{{- if .Values.minio.auth.existingSecret }}
|
||||
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootUser" "value" (dict "value" .Values.minio.auth.rootUser "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootUserKey )) | nindent 2 }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Release.Name }}-minio
|
||||
key: {{ .Values.minio.auth.rootUserKey }}
|
||||
{{- end }}
|
||||
- name: STORAGE_S3_SECRET_KEY
|
||||
{{- if .Values.minio.auth.existingSecret }}
|
||||
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "minio.auth.rootPassword" "value" (dict "value" .Values.minio.auth.rootPassword "existingSecret" .Values.minio.auth.existingSecret "secretKey" .Values.minio.auth.rootPasswordKey )) | nindent 2 }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Release.Name }}-minio
|
||||
key: {{ .Values.minio.auth.rootPasswordKey }}
|
||||
{{- end }}
|
||||
value: {{ if $endpoint }}{{ $endpoint | quote }}{{ else }}{{ printf "http://%s:9000" (include "colanode.minio.hostname" .) | quote }}{{ end }}
|
||||
- name: STORAGE_S3_BUCKET
|
||||
value: "colanode"
|
||||
value: {{ required "colanode.storage.s3.bucket must be set when STORAGE_TYPE is s3" $s3.bucket | quote }}
|
||||
- name: STORAGE_S3_REGION
|
||||
value: "us-east-1"
|
||||
value: {{ required "colanode.storage.s3.region must be set when STORAGE_TYPE is s3" $s3.region | quote }}
|
||||
- name: STORAGE_S3_FORCE_PATH_STYLE
|
||||
value: "true"
|
||||
value: {{ ternary "true" "false" (default true $s3.forcePathStyle) | quote }}
|
||||
- name: STORAGE_S3_ACCESS_KEY
|
||||
{{- if or $s3.accessKey.value $s3.accessKey.existingSecret }}
|
||||
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "colanode.storage.s3.accessKey" "value" $s3.accessKey) | nindent 2 }}
|
||||
{{- else if .Values.minio.enabled }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ if .Values.minio.auth.existingSecret }}{{ .Values.minio.auth.existingSecret }}{{ else }}{{ printf "%s-minio" .Release.Name }}{{ end }}
|
||||
key: {{ .Values.minio.auth.rootUserKey }}
|
||||
{{- else }}
|
||||
{{- fail "An S3 access key must be provided via colanode.storage.s3.accessKey when STORAGE_TYPE is s3 and MinIO is disabled" }}
|
||||
{{- end }}
|
||||
- name: STORAGE_S3_SECRET_KEY
|
||||
{{- if or $s3.secretKey.value $s3.secretKey.existingSecret }}
|
||||
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "colanode.storage.s3.secretKey" "value" $s3.secretKey) | nindent 2 }}
|
||||
{{- else if .Values.minio.enabled }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ if .Values.minio.auth.existingSecret }}{{ .Values.minio.auth.existingSecret }}{{ else }}{{ printf "%s-minio" .Release.Name }}{{ end }}
|
||||
key: {{ .Values.minio.auth.rootPasswordKey }}
|
||||
{{- else }}
|
||||
{{- fail "An S3 secret key must be provided via colanode.storage.s3.secretKey when STORAGE_TYPE is s3 and MinIO is disabled" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $storageType "gcs" }}
|
||||
{{- $gcs := .Values.colanode.storage.gcs }}
|
||||
- name: STORAGE_GCS_BUCKET
|
||||
value: {{ required "colanode.storage.gcs.bucket must be set when STORAGE_TYPE is gcs" $gcs.bucket | quote }}
|
||||
- name: STORAGE_GCS_PROJECT_ID
|
||||
value: {{ required "colanode.storage.gcs.projectId must be set when STORAGE_TYPE is gcs" $gcs.projectId | quote }}
|
||||
{{- if $gcs.credentialsSecret.name }}
|
||||
- name: STORAGE_GCS_CREDENTIALS
|
||||
value: {{ printf "%s/%s" (trimSuffix "/" $gcs.credentialsSecret.mountPath) $gcs.credentialsSecret.fileName | quote }}
|
||||
{{- else if $gcs.credentialsPath }}
|
||||
- name: STORAGE_GCS_CREDENTIALS
|
||||
value: {{ $gcs.credentialsPath | quote }}
|
||||
{{- else }}
|
||||
{{- fail "Provide colanode.storage.gcs.credentialsSecret or credentialsPath when STORAGE_TYPE is gcs" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $storageType "azure" }}
|
||||
{{- $azure := .Values.colanode.storage.azure }}
|
||||
- name: STORAGE_AZURE_ACCOUNT
|
||||
value: {{ required "colanode.storage.azure.account must be set when STORAGE_TYPE is azure" $azure.account | quote }}
|
||||
- name: STORAGE_AZURE_CONTAINER_NAME
|
||||
value: {{ required "colanode.storage.azure.containerName must be set when STORAGE_TYPE is azure" $azure.containerName | quote }}
|
||||
- name: STORAGE_AZURE_ACCOUNT_KEY
|
||||
{{- if or $azure.accountKey.value $azure.accountKey.existingSecret }}
|
||||
{{- include "colanode.getRequiredValueOrSecret" (dict "key" "colanode.storage.azure.accountKey" "value" $azure.accountKey) | nindent 2 }}
|
||||
{{- else }}
|
||||
{{- fail "An Azure storage account key must be provided via colanode.storage.azure.accountKey when STORAGE_TYPE is azure" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# SMTP configuration
|
||||
@@ -258,4 +312,4 @@ Colanode Server Environment Variables
|
||||
- name: SMTP_EMAIL_FROM_NAME
|
||||
value: {{ .Values.colanode.config.SMTP_EMAIL_FROM_NAME | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -38,5 +38,47 @@ spec:
|
||||
protocol: TCP
|
||||
env:
|
||||
{{- include "colanode.serverEnvVars" . | nindent 12 }}
|
||||
{{- $storageType := default "file" .Values.colanode.storage.type }}
|
||||
{{- $mountFileStorage := eq $storageType "file" }}
|
||||
{{- $gcsSecret := .Values.colanode.storage.gcs.credentialsSecret }}
|
||||
{{- $mountGcsCredentials := and (eq $storageType "gcs") $gcsSecret.name }}
|
||||
{{- if or $mountFileStorage $mountGcsCredentials }}
|
||||
volumeMounts:
|
||||
{{- if $mountFileStorage }}
|
||||
- name: storage-data
|
||||
mountPath: {{ required "colanode.storage.file.directory must be set when STORAGE_TYPE is file" .Values.colanode.storage.file.directory }}
|
||||
{{- end }}
|
||||
{{- if $mountGcsCredentials }}
|
||||
- name: gcs-credentials
|
||||
mountPath: {{ required "colanode.storage.gcs.credentialsSecret.mountPath must be set when mounting GCS credentials" $gcsSecret.mountPath }}
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.colanode.resources | nindent 12 }}
|
||||
{{- if or $mountFileStorage $mountGcsCredentials }}
|
||||
volumes:
|
||||
{{- if $mountFileStorage }}
|
||||
- name: storage-data
|
||||
{{- $persistence := .Values.colanode.storage.file.persistence }}
|
||||
{{- if $persistence.enabled }}
|
||||
{{- if $persistence.existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ $persistence.existingClaim }}
|
||||
{{- else }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "colanode.storagePvcName" . }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $mountGcsCredentials }}
|
||||
- name: gcs-credentials
|
||||
secret:
|
||||
secretName: {{ $gcsSecret.name }}
|
||||
items:
|
||||
- key: {{ required "colanode.storage.gcs.credentialsSecret.key must be set when providing a secret" $gcsSecret.key }}
|
||||
path: {{ required "colanode.storage.gcs.credentialsSecret.fileName must be set when providing a secret" $gcsSecret.fileName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
21
hosting/kubernetes/chart/templates/storage-pvc.yaml
Normal file
21
hosting/kubernetes/chart/templates/storage-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- $storageType := default "file" .Values.colanode.storage.type -}}
|
||||
{{- $persistence := .Values.colanode.storage.file.persistence -}}
|
||||
{{- if and (eq $storageType "file") $persistence.enabled (not $persistence.existingClaim) -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "colanode.storagePvcName" . }}
|
||||
labels:
|
||||
{{- include "colanode.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
{{- range $persistence.accessModes }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ required "colanode.storage.file.persistence.size must be set when creating a storage PVC" $persistence.size }}
|
||||
{{- if $persistence.storageClass }}
|
||||
storageClassName: {{ $persistence.storageClass }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -114,14 +114,6 @@ colanode:
|
||||
REDIS_TUS_KV_PREFIX: 'colanode:tus:kv'
|
||||
REDIS_EVENTS_CHANNEL: 'events'
|
||||
|
||||
# S3 storage for files
|
||||
STORAGE_S3_ENDPOINT: 'http://{{ .Release.Name }}-minio:9000'
|
||||
STORAGE_S3_ACCESS_KEY: 'minioadmin'
|
||||
STORAGE_S3_SECRET_KEY: '$(MINIO_ROOT_PASSWORD)'
|
||||
STORAGE_S3_BUCKET: 'colanode'
|
||||
STORAGE_S3_REGION: 'us-east-1'
|
||||
STORAGE_S3_FORCE_PATH_STYLE: 'true'
|
||||
|
||||
# Email configuration
|
||||
SMTP_ENABLED: 'false'
|
||||
# SMTP_HOST: ""
|
||||
@@ -131,6 +123,80 @@ colanode:
|
||||
# SMTP_EMAIL_FROM: ""
|
||||
# SMTP_EMAIL_FROM_NAME: "Colanode"
|
||||
|
||||
storage:
|
||||
# -- Storage backend type. Supported values: file, s3, gcs, azure
|
||||
type: file
|
||||
|
||||
file:
|
||||
# -- Directory inside the pod where files are stored when using file storage
|
||||
directory: /var/lib/colanode/storage
|
||||
persistence:
|
||||
# -- Enable a PersistentVolumeClaim for file storage
|
||||
enabled: true
|
||||
# -- Use an existing PVC instead of creating one
|
||||
existingClaim: ''
|
||||
# -- Requested storage size for the PVC (only when creating a new claim)
|
||||
size: 20Gi
|
||||
# -- Storage class for the PVC. Leave empty to use the cluster default
|
||||
storageClass: ''
|
||||
# -- Access modes for the PVC
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
|
||||
s3:
|
||||
# -- Custom endpoint for S3-compatible storage. Leave empty to use the bundled MinIO service hostname
|
||||
endpoint: ''
|
||||
# -- Bucket name to use when STORAGE_TYPE is s3
|
||||
bucket: colanode
|
||||
# -- Region for the S3 bucket
|
||||
region: us-east-1
|
||||
# -- Force path-style URLs (required for MinIO and most S3-compatible providers)
|
||||
forcePathStyle: true
|
||||
accessKey:
|
||||
# -- Optional plain-text access key (prefer using secrets in production)
|
||||
value: ''
|
||||
# -- Name of an existing secret that stores the access key (takes precedence over value)
|
||||
existingSecret: ''
|
||||
# -- Key within the existing secret that stores the access key
|
||||
secretKey: ''
|
||||
secretKey:
|
||||
# -- Optional plain-text secret key (prefer using secrets in production)
|
||||
value: ''
|
||||
# -- Name of an existing secret that stores the secret key (takes precedence over value)
|
||||
existingSecret: ''
|
||||
# -- Key within the existing secret that stores the secret key
|
||||
secretKey: ''
|
||||
|
||||
gcs:
|
||||
# -- Bucket name when STORAGE_TYPE is gcs
|
||||
bucket: ''
|
||||
# -- GCP project ID associated with the bucket
|
||||
projectId: ''
|
||||
# -- Optional direct path to the credentials file (used when credentialsSecret.name is empty)
|
||||
credentialsPath: ''
|
||||
credentialsSecret:
|
||||
# -- Name of the secret that contains the GCP service account JSON key
|
||||
name: ''
|
||||
# -- Key inside the secret that stores the JSON key
|
||||
key: ''
|
||||
# -- Directory where the secret will be mounted
|
||||
mountPath: /var/secrets/gcp
|
||||
# -- File name to use when mounting the JSON key
|
||||
fileName: service-account.json
|
||||
|
||||
azure:
|
||||
# -- Storage account name when STORAGE_TYPE is azure
|
||||
account: ''
|
||||
# -- Blob container name when STORAGE_TYPE is azure
|
||||
containerName: ''
|
||||
accountKey:
|
||||
# -- Optional plain-text storage account key
|
||||
value: ''
|
||||
# -- Name of an existing secret that stores the account key (takes precedence over value)
|
||||
existingSecret: ''
|
||||
# -- Key within the existing secret that stores the account key
|
||||
secretKey: ''
|
||||
|
||||
global:
|
||||
security:
|
||||
# Required for custom PostgreSQL image with pgvector extension
|
||||
@@ -178,6 +244,7 @@ redis:
|
||||
|
||||
# MinIO object storage
|
||||
minio:
|
||||
enabled: false
|
||||
auth:
|
||||
rootUser: 'minioadmin'
|
||||
# password: "" # Leave empty to auto-generate
|
||||
|
||||
690
package-lock.json
generated
690
package-lock.json
generated
@@ -76,12 +76,16 @@
|
||||
"@colanode/crdt": "*",
|
||||
"@fastify/cors": "^11.1.0",
|
||||
"@fastify/websocket": "^11.2.0",
|
||||
"@google-cloud/storage": "^7.15.0",
|
||||
"@langchain/core": "^0.3.78",
|
||||
"@langchain/google-genai": "^0.2.18",
|
||||
"@langchain/langgraph": "^0.4.9",
|
||||
"@langchain/openai": "^0.6.14",
|
||||
"@node-rs/argon2": "^2.0.2",
|
||||
"@redis/client": "^5.8.3",
|
||||
"@tus/azure-store": "^2.0.0",
|
||||
"@tus/file-store": "^2.0.0",
|
||||
"@tus/gcs-store": "^2.0.0",
|
||||
"@tus/s3-store": "^2.0.1",
|
||||
"@tus/server": "^2.3.0",
|
||||
"bullmq": "^5.61.0",
|
||||
@@ -1719,6 +1723,206 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-auth": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz",
|
||||
"integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-util": "^1.13.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-client": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
|
||||
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.10.0",
|
||||
"@azure/core-rest-pipeline": "^1.22.0",
|
||||
"@azure/core-tracing": "^1.3.0",
|
||||
"@azure/core-util": "^1.13.0",
|
||||
"@azure/logger": "^1.3.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-http-compat": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.1.tgz",
|
||||
"integrity": "sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-client": "^1.10.0",
|
||||
"@azure/core-rest-pipeline": "^1.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-lro": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
|
||||
"integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-util": "^1.2.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-paging": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
|
||||
"integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz",
|
||||
"integrity": "sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.10.0",
|
||||
"@azure/core-tracing": "^1.3.0",
|
||||
"@azure/core-util": "^1.13.0",
|
||||
"@azure/logger": "^1.3.0",
|
||||
"@typespec/ts-http-runtime": "^0.3.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-tracing": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz",
|
||||
"integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-util": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz",
|
||||
"integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@typespec/ts-http-runtime": "^0.3.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-xml": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.0.tgz",
|
||||
"integrity": "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-parser": "^5.0.7",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/logger": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz",
|
||||
"integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typespec/ts-http-runtime": "^0.3.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob": {
|
||||
"version": "12.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.28.0.tgz",
|
||||
"integrity": "sha512-VhQHITXXO03SURhDiGuHhvc/k/sD2WvJUS7hqhiVNbErVCuQoLtWql7r97fleBlIRKHJaa9R7DpBjfE0pfLYcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.9.0",
|
||||
"@azure/core-client": "^1.9.3",
|
||||
"@azure/core-http-compat": "^2.2.0",
|
||||
"@azure/core-lro": "^2.2.0",
|
||||
"@azure/core-paging": "^1.6.2",
|
||||
"@azure/core-rest-pipeline": "^1.19.1",
|
||||
"@azure/core-tracing": "^1.2.0",
|
||||
"@azure/core-util": "^1.11.0",
|
||||
"@azure/core-xml": "^1.4.5",
|
||||
"@azure/logger": "^1.1.4",
|
||||
"@azure/storage-common": "^12.0.0-beta.2",
|
||||
"events": "^3.0.0",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/storage-common": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-common/-/storage-common-12.0.0.tgz",
|
||||
"integrity": "sha512-QyEWXgi4kdRo0wc1rHum9/KnaWZKCdQGZK1BjU4fFL6Jtedp7KLbQihgTTVxldFy1z1ZPtuDPx8mQ5l3huPPbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.1.2",
|
||||
"@azure/core-auth": "^1.9.0",
|
||||
"@azure/core-http-compat": "^2.2.0",
|
||||
"@azure/core-rest-pipeline": "^1.19.1",
|
||||
"@azure/core-tracing": "^1.2.0",
|
||||
"@azure/core-util": "^1.11.0",
|
||||
"@azure/logger": "^1.1.4",
|
||||
"events": "^3.3.0",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
@@ -5527,6 +5731,102 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@google-cloud/paginator": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
|
||||
"integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"arrify": "^2.0.0",
|
||||
"extend": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/projectify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
|
||||
"integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/promisify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
|
||||
"integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/storage": {
|
||||
"version": "7.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.1.tgz",
|
||||
"integrity": "sha512-2FMQbpU7qK+OtBPaegC6n+XevgZksobUGo6mGKnXNmeZpvLiAo1gTAE3oTKsrMGDV4VtL8Zzpono0YsK/Q7Iqg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google-cloud/paginator": "^5.0.0",
|
||||
"@google-cloud/projectify": "^4.0.0",
|
||||
"@google-cloud/promisify": "<4.1.0",
|
||||
"abort-controller": "^3.0.0",
|
||||
"async-retry": "^1.3.3",
|
||||
"duplexify": "^4.1.3",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"gaxios": "^6.0.2",
|
||||
"google-auth-library": "^9.6.3",
|
||||
"html-entities": "^2.5.2",
|
||||
"mime": "^3.0.0",
|
||||
"p-limit": "^3.0.1",
|
||||
"retry-request": "^7.0.0",
|
||||
"teeny-request": "^9.0.0",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/storage/node_modules/fast-xml-parser": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
|
||||
"integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/storage/node_modules/strnum": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
|
||||
"integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@google-cloud/storage/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
|
||||
@@ -10770,7 +11070,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
@@ -10785,6 +11084,67 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tus/azure-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tus/azure-store/-/azure-store-2.0.0.tgz",
|
||||
"integrity": "sha512-V9oPt5YqtTmQzQ3MAY6t4SoZVc665E/6SaK1QffFo7MRCF16TE7fRie5LsTkNX9Zf9vEwUbdQswZSNYPKuGi1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/storage-blob": "^12.24.0",
|
||||
"@tus/utils": "^0.6.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tus/file-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tus/file-store/-/file-store-2.0.0.tgz",
|
||||
"integrity": "sha512-LTh9L/RoWoo2TbBGPZOuhuyEIIqweoTekT77ZkIVkpYkLK8zTt++PRdY+VyJsLDbFMO9RzvKSBRmj1H8SPdDew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tus/utils": "^0.6.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@redis/client": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tus/file-store/node_modules/@redis/client": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2",
|
||||
"generic-pool": "3.9.0",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@tus/gcs-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tus/gcs-store/-/gcs-store-2.0.0.tgz",
|
||||
"integrity": "sha512-YCSSI8PesTSFEmfJaYBHRN+kvmPBElW+w2w4ODr3JsemH2JTeEbc47BgOC9PgEIEMdo+3FgmmyUcq5jsyO2vmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tus/utils": "^0.6.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@google-cloud/storage": "^7.15.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tus/s3-store": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@tus/s3-store/-/s3-store-2.0.1.tgz",
|
||||
@@ -11519,6 +11879,20 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typespec/ts-http-runtime": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz",
|
||||
"integrity": "sha512-SnbaqayTVFEA6/tYumdF0UmybY0KHyKwGPBXnyckFlrrKdhWFrL3a2HIPXHjht5ZOElKGcXfD2D63P36btb+ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
||||
@@ -11688,6 +12062,18 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
@@ -11730,7 +12116,6 @@
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -12101,6 +12486,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/arrify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
|
||||
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -12135,6 +12529,15 @@
|
||||
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-retry": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
|
||||
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"retry": "0.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
@@ -12300,6 +12703,15 @@
|
||||
"node": "20.x || 22.x || 23.x || 24.x"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -12469,6 +12881,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@@ -13946,6 +14364,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
@@ -14583,7 +15010,6 @@
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -15364,12 +15790,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
||||
@@ -15495,6 +15939,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
@@ -16194,6 +16644,48 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/gaxios": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
|
||||
"integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"extend": "^3.0.2",
|
||||
"https-proxy-agent": "^7.0.1",
|
||||
"is-stream": "^2.0.0",
|
||||
"node-fetch": "^2.6.9",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/gaxios/node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gcp-metadata": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
|
||||
"integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"gaxios": "^6.1.1",
|
||||
"google-logging-utils": "^0.0.2",
|
||||
"json-bigint": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
@@ -16531,6 +17023,32 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library": {
|
||||
"version": "9.15.1",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
|
||||
"integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
"gaxios": "^6.1.1",
|
||||
"gcp-metadata": "^6.1.0",
|
||||
"gtoken": "^7.0.0",
|
||||
"jws": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/google-logging-utils": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
|
||||
"integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -16583,6 +17101,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gtoken": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gaxios": "^6.0.0",
|
||||
"jws": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
@@ -16723,6 +17254,22 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
|
||||
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/mdevils"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
@@ -16734,7 +17281,6 @@
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.0",
|
||||
@@ -16762,7 +17308,6 @@
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
@@ -16786,7 +17331,6 @@
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -17757,6 +18301,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
@@ -17891,6 +18444,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
||||
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -18994,6 +19568,18 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -19427,7 +20013,6 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
@@ -19448,21 +20033,18 @@
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
@@ -20114,7 +20696,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
@@ -21922,6 +22503,20 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/retry-request": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
|
||||
"integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/request": "^2.48.8",
|
||||
"extend": "^3.0.2",
|
||||
"teeny-request": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
@@ -22151,7 +22746,7 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
@@ -22826,6 +23421,15 @@
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-events": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
|
||||
"integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"stubs": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-shift": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
|
||||
@@ -23164,6 +23768,12 @@
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/stubs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
|
||||
"integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
@@ -23465,6 +24075,61 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/teeny-request": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
|
||||
"integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"node-fetch": "^2.6.9",
|
||||
"stream-events": "^1.0.5",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/teeny-request/node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/teeny-request/node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/teeny-request/node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/temp": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
|
||||
@@ -25843,7 +26508,6 @@
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
||||
Reference in New Issue
Block a user