Release: CI/CD Pipeline & Architecture Updates #177
@@ -36,10 +36,14 @@
|
||||
"@nestjs/websockets": "^11.1.12",
|
||||
"@prisma/client": "^6.19.2",
|
||||
"@types/marked": "^6.0.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"archiver": "^7.0.1",
|
||||
"better-auth": "^1.4.17",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"highlight.js": "^11.11.1",
|
||||
"ioredis": "^5.9.2",
|
||||
"marked": "^17.0.1",
|
||||
"marked-gfm-heading-id": "^4.1.3",
|
||||
"marked-highlight": "^2.2.3",
|
||||
@@ -57,9 +61,11 @@
|
||||
"@nestjs/schematics": "^11.0.1",
|
||||
"@nestjs/testing": "^11.1.12",
|
||||
"@swc/core": "^1.10.18",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/archiver": "^7.0.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/highlight.js": "^10.1.0",
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
|
||||
51
apps/api/src/knowledge/dto/import-export.dto.ts
Normal file
51
apps/api/src/knowledge/dto/import-export.dto.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsEnum,
|
||||
IsArray,
|
||||
} from "class-validator";
|
||||
|
||||
/**
|
||||
* Export format enum
|
||||
*/
|
||||
export enum ExportFormat {
|
||||
MARKDOWN = "markdown",
|
||||
JSON = "json",
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for export query parameters
|
||||
*/
|
||||
export class ExportQueryDto {
|
||||
@IsOptional()
|
||||
@IsEnum(ExportFormat, { message: "format must be either 'markdown' or 'json'" })
|
||||
format?: ExportFormat = ExportFormat.MARKDOWN;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray({ message: "entryIds must be an array" })
|
||||
@IsString({ each: true, message: "each entryId must be a string" })
|
||||
entryIds?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import result for a single entry
|
||||
*/
|
||||
export interface ImportResult {
|
||||
filename: string;
|
||||
success: boolean;
|
||||
entryId?: string;
|
||||
slug?: string;
|
||||
title?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response DTO for import operation
|
||||
*/
|
||||
export interface ImportResponseDto {
|
||||
success: boolean;
|
||||
totalFiles: number;
|
||||
imported: number;
|
||||
failed: number;
|
||||
results: ImportResult[];
|
||||
}
|
||||
@@ -10,3 +10,5 @@ export {
|
||||
RecentEntriesDto,
|
||||
} from "./search-query.dto";
|
||||
export { GraphQueryDto } from "./graph-query.dto";
|
||||
export { ExportQueryDto, ExportFormat } from "./import-export.dto";
|
||||
export type { ImportResult, ImportResponseDto } from "./import-export.dto";
|
||||
|
||||
126
apps/api/src/knowledge/import-export.controller.ts
Normal file
126
apps/api/src/knowledge/import-export.controller.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
Query,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
Res,
|
||||
BadRequestException,
|
||||
} from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { Response } from "express";
|
||||
import { ImportExportService } from "./services/import-export.service";
|
||||
import { ExportQueryDto, ExportFormat, ImportResponseDto } from "./dto";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
|
||||
import { Workspace, Permission, RequirePermission } from "../common/decorators";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
import type { AuthUser } from "../auth/types/better-auth-request.interface";
|
||||
|
||||
/**
|
||||
* Controller for knowledge import/export endpoints
|
||||
* All endpoints require authentication and workspace context
|
||||
*/
|
||||
@Controller("knowledge")
|
||||
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
|
||||
export class ImportExportController {
|
||||
constructor(private readonly importExportService: ImportExportService) {}
|
||||
|
||||
/**
|
||||
* POST /api/knowledge/import
|
||||
* Import knowledge entries from uploaded file (.md or .zip)
|
||||
* Requires: MEMBER role or higher
|
||||
*/
|
||||
@Post("import")
|
||||
@RequirePermission(Permission.WORKSPACE_MEMBER)
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: {
|
||||
fileSize: 50 * 1024 * 1024, // 50MB max file size
|
||||
},
|
||||
fileFilter: (_req, file, callback) => {
|
||||
// Only accept .md and .zip files
|
||||
const allowedMimeTypes = [
|
||||
"text/markdown",
|
||||
"application/zip",
|
||||
"application/x-zip-compressed",
|
||||
];
|
||||
const allowedExtensions = [".md", ".zip"];
|
||||
const fileExtension = file.originalname.toLowerCase().slice(
|
||||
file.originalname.lastIndexOf(".")
|
||||
);
|
||||
|
||||
if (
|
||||
allowedMimeTypes.includes(file.mimetype) ||
|
||||
allowedExtensions.includes(fileExtension)
|
||||
) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(
|
||||
new BadRequestException(
|
||||
"Invalid file type. Only .md and .zip files are accepted."
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
async importEntries(
|
||||
@Workspace() workspaceId: string,
|
||||
@CurrentUser() user: AuthUser,
|
||||
@UploadedFile() file: Express.Multer.File
|
||||
): Promise<ImportResponseDto> {
|
||||
if (!file) {
|
||||
throw new BadRequestException("No file uploaded");
|
||||
}
|
||||
|
||||
const result = await this.importExportService.importEntries(
|
||||
workspaceId,
|
||||
user.id,
|
||||
file
|
||||
);
|
||||
|
||||
return {
|
||||
success: result.failed === 0,
|
||||
totalFiles: result.totalFiles,
|
||||
imported: result.imported,
|
||||
failed: result.failed,
|
||||
results: result.results,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/knowledge/export
|
||||
* Export knowledge entries as a zip file
|
||||
* Query params:
|
||||
* - format: 'markdown' (default) or 'json'
|
||||
* - entryIds: optional array of entry IDs to export (exports all if not provided)
|
||||
* Requires: Any workspace member
|
||||
*/
|
||||
@Get("export")
|
||||
@RequirePermission(Permission.WORKSPACE_ANY)
|
||||
async exportEntries(
|
||||
@Workspace() workspaceId: string,
|
||||
@Query() query: ExportQueryDto,
|
||||
@Res() res: Response
|
||||
): Promise<void> {
|
||||
const format = query.format || ExportFormat.MARKDOWN;
|
||||
const entryIds = query.entryIds;
|
||||
|
||||
const { stream, filename } = await this.importExportService.exportEntries(
|
||||
workspaceId,
|
||||
format,
|
||||
entryIds
|
||||
);
|
||||
|
||||
// Set response headers
|
||||
res.setHeader("Content-Type", "application/zip");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
||||
|
||||
// Pipe the archive stream to response
|
||||
stream.pipe(res);
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,24 @@ import { KnowledgeService } from "./knowledge.service";
|
||||
import { KnowledgeController } from "./knowledge.controller";
|
||||
import { SearchController } from "./search.controller";
|
||||
import { KnowledgeStatsController } from "./stats.controller";
|
||||
import { ImportExportController } from "./import-export.controller";
|
||||
import {
|
||||
LinkResolutionService,
|
||||
SearchService,
|
||||
LinkSyncService,
|
||||
GraphService,
|
||||
StatsService,
|
||||
ImportExportService,
|
||||
} from "./services";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, AuthModule],
|
||||
controllers: [KnowledgeController, SearchController, KnowledgeStatsController],
|
||||
controllers: [
|
||||
KnowledgeController,
|
||||
SearchController,
|
||||
KnowledgeStatsController,
|
||||
ImportExportController,
|
||||
],
|
||||
providers: [
|
||||
KnowledgeService,
|
||||
LinkResolutionService,
|
||||
@@ -23,6 +30,7 @@ import {
|
||||
LinkSyncService,
|
||||
GraphService,
|
||||
StatsService,
|
||||
ImportExportService,
|
||||
],
|
||||
exports: [KnowledgeService, LinkResolutionService, SearchService],
|
||||
})
|
||||
|
||||
297
apps/api/src/knowledge/services/import-export.service.spec.ts
Normal file
297
apps/api/src/knowledge/services/import-export.service.spec.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ImportExportService } from "./import-export.service";
|
||||
import { KnowledgeService } from "../knowledge.service";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
import { ExportFormat } from "../dto";
|
||||
import { EntryStatus, Visibility } from "@prisma/client";
|
||||
|
||||
describe("ImportExportService", () => {
|
||||
let service: ImportExportService;
|
||||
let knowledgeService: KnowledgeService;
|
||||
let prisma: PrismaService;
|
||||
|
||||
const workspaceId = "workspace-123";
|
||||
const userId = "user-123";
|
||||
|
||||
const mockEntry = {
|
||||
id: "entry-123",
|
||||
workspaceId,
|
||||
slug: "test-entry",
|
||||
title: "Test Entry",
|
||||
content: "Test content",
|
||||
summary: "Test summary",
|
||||
status: EntryStatus.PUBLISHED,
|
||||
visibility: Visibility.WORKSPACE,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
tags: [
|
||||
{
|
||||
tag: {
|
||||
id: "tag-1",
|
||||
name: "TypeScript",
|
||||
slug: "typescript",
|
||||
color: "#3178c6",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockKnowledgeService = {
|
||||
create: vi.fn(),
|
||||
findAll: vi.fn(),
|
||||
};
|
||||
|
||||
const mockPrismaService = {
|
||||
knowledgeEntry: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ImportExportService,
|
||||
{
|
||||
provide: KnowledgeService,
|
||||
useValue: mockKnowledgeService,
|
||||
},
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: mockPrismaService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ImportExportService>(ImportExportService);
|
||||
knowledgeService = module.get<KnowledgeService>(KnowledgeService);
|
||||
prisma = module.get<PrismaService>(PrismaService);
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("importEntries", () => {
|
||||
it("should import a single markdown file successfully", async () => {
|
||||
const markdown = `---
|
||||
title: Test Entry
|
||||
status: PUBLISHED
|
||||
tags:
|
||||
- TypeScript
|
||||
- Testing
|
||||
---
|
||||
|
||||
This is the content of the entry.`;
|
||||
|
||||
const file: Express.Multer.File = {
|
||||
fieldname: "file",
|
||||
originalname: "test.md",
|
||||
encoding: "utf-8",
|
||||
mimetype: "text/markdown",
|
||||
size: markdown.length,
|
||||
buffer: Buffer.from(markdown),
|
||||
stream: null as any,
|
||||
destination: "",
|
||||
filename: "",
|
||||
path: "",
|
||||
};
|
||||
|
||||
mockKnowledgeService.create.mockResolvedValue({
|
||||
id: "entry-123",
|
||||
slug: "test-entry",
|
||||
title: "Test Entry",
|
||||
});
|
||||
|
||||
const result = await service.importEntries(workspaceId, userId, file);
|
||||
|
||||
expect(result.totalFiles).toBe(1);
|
||||
expect(result.imported).toBe(1);
|
||||
expect(result.failed).toBe(0);
|
||||
expect(result.results[0].success).toBe(true);
|
||||
expect(result.results[0].title).toBe("Test Entry");
|
||||
expect(mockKnowledgeService.create).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
userId,
|
||||
expect.objectContaining({
|
||||
title: "Test Entry",
|
||||
content: "This is the content of the entry.",
|
||||
status: EntryStatus.PUBLISHED,
|
||||
tags: ["TypeScript", "Testing"],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use filename as title if frontmatter title is missing", async () => {
|
||||
const markdown = `This is content without frontmatter.`;
|
||||
|
||||
const file: Express.Multer.File = {
|
||||
fieldname: "file",
|
||||
originalname: "my-entry.md",
|
||||
encoding: "utf-8",
|
||||
mimetype: "text/markdown",
|
||||
size: markdown.length,
|
||||
buffer: Buffer.from(markdown),
|
||||
stream: null as any,
|
||||
destination: "",
|
||||
filename: "",
|
||||
path: "",
|
||||
};
|
||||
|
||||
mockKnowledgeService.create.mockResolvedValue({
|
||||
id: "entry-123",
|
||||
slug: "my-entry",
|
||||
title: "my-entry",
|
||||
});
|
||||
|
||||
const result = await service.importEntries(workspaceId, userId, file);
|
||||
|
||||
expect(result.imported).toBe(1);
|
||||
expect(mockKnowledgeService.create).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
userId,
|
||||
expect.objectContaining({
|
||||
title: "my-entry",
|
||||
content: "This is content without frontmatter.",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject invalid file types", async () => {
|
||||
const file: Express.Multer.File = {
|
||||
fieldname: "file",
|
||||
originalname: "test.txt",
|
||||
encoding: "utf-8",
|
||||
mimetype: "text/plain",
|
||||
size: 100,
|
||||
buffer: Buffer.from("test"),
|
||||
stream: null as any,
|
||||
destination: "",
|
||||
filename: "",
|
||||
path: "",
|
||||
};
|
||||
|
||||
await expect(
|
||||
service.importEntries(workspaceId, userId, file)
|
||||
).rejects.toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it("should handle import errors gracefully", async () => {
|
||||
const markdown = `---
|
||||
title: Test Entry
|
||||
---
|
||||
|
||||
Content`;
|
||||
|
||||
const file: Express.Multer.File = {
|
||||
fieldname: "file",
|
||||
originalname: "test.md",
|
||||
encoding: "utf-8",
|
||||
mimetype: "text/markdown",
|
||||
size: markdown.length,
|
||||
buffer: Buffer.from(markdown),
|
||||
stream: null as any,
|
||||
destination: "",
|
||||
filename: "",
|
||||
path: "",
|
||||
};
|
||||
|
||||
mockKnowledgeService.create.mockRejectedValue(
|
||||
new Error("Database error")
|
||||
);
|
||||
|
||||
const result = await service.importEntries(workspaceId, userId, file);
|
||||
|
||||
expect(result.totalFiles).toBe(1);
|
||||
expect(result.imported).toBe(0);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.results[0].success).toBe(false);
|
||||
expect(result.results[0].error).toBe("Database error");
|
||||
});
|
||||
|
||||
it("should reject empty markdown content", async () => {
|
||||
const markdown = `---
|
||||
title: Empty Entry
|
||||
---
|
||||
|
||||
`;
|
||||
|
||||
const file: Express.Multer.File = {
|
||||
fieldname: "file",
|
||||
originalname: "empty.md",
|
||||
encoding: "utf-8",
|
||||
mimetype: "text/markdown",
|
||||
size: markdown.length,
|
||||
buffer: Buffer.from(markdown),
|
||||
stream: null as any,
|
||||
destination: "",
|
||||
filename: "",
|
||||
path: "",
|
||||
};
|
||||
|
||||
const result = await service.importEntries(workspaceId, userId, file);
|
||||
|
||||
expect(result.imported).toBe(0);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.results[0].error).toBe("Empty content");
|
||||
});
|
||||
});
|
||||
|
||||
describe("exportEntries", () => {
|
||||
it("should export entries as markdown format", async () => {
|
||||
mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]);
|
||||
|
||||
const result = await service.exportEntries(
|
||||
workspaceId,
|
||||
ExportFormat.MARKDOWN
|
||||
);
|
||||
|
||||
expect(result.filename).toMatch(/knowledge-export-\d{4}-\d{2}-\d{2}\.zip/);
|
||||
expect(result.stream).toBeDefined();
|
||||
expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({
|
||||
where: { workspaceId },
|
||||
include: {
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
title: "asc",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should export only specified entries", async () => {
|
||||
const entryIds = ["entry-123", "entry-456"];
|
||||
mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]);
|
||||
|
||||
await service.exportEntries(workspaceId, ExportFormat.JSON, entryIds);
|
||||
|
||||
expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId,
|
||||
id: { in: entryIds },
|
||||
},
|
||||
include: {
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
title: "asc",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw error when no entries found", async () => {
|
||||
mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
service.exportEntries(workspaceId, ExportFormat.MARKDOWN)
|
||||
).rejects.toThrow(BadRequestException);
|
||||
});
|
||||
});
|
||||
});
|
||||
372
apps/api/src/knowledge/services/import-export.service.ts
Normal file
372
apps/api/src/knowledge/services/import-export.service.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { Injectable, BadRequestException } from "@nestjs/common";
|
||||
import { EntryStatus, Visibility } from "@prisma/client";
|
||||
import archiver from "archiver";
|
||||
import AdmZip from "adm-zip";
|
||||
import matter from "gray-matter";
|
||||
import { Readable } from "stream";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
import { KnowledgeService } from "../knowledge.service";
|
||||
import type { ExportFormat, ImportResult } from "../dto";
|
||||
import type { CreateEntryDto } from "../dto/create-entry.dto";
|
||||
|
||||
interface ExportEntry {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
content: string;
|
||||
summary: string | null;
|
||||
status: EntryStatus;
|
||||
visibility: Visibility;
|
||||
tags: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for handling knowledge entry import/export operations
|
||||
*/
|
||||
@Injectable()
|
||||
export class ImportExportService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly knowledgeService: KnowledgeService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Import entries from uploaded file(s)
|
||||
* Accepts single .md file or .zip containing multiple .md files
|
||||
*/
|
||||
async importEntries(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
file: Express.Multer.File
|
||||
): Promise<{ results: ImportResult[]; totalFiles: number; imported: number; failed: number }> {
|
||||
const results: ImportResult[] = [];
|
||||
|
||||
try {
|
||||
if (file.mimetype === "text/markdown" || file.originalname.endsWith(".md")) {
|
||||
// Single markdown file
|
||||
const result = await this.importSingleMarkdown(
|
||||
workspaceId,
|
||||
userId,
|
||||
file.originalname,
|
||||
file.buffer.toString("utf-8")
|
||||
);
|
||||
results.push(result);
|
||||
} else if (
|
||||
file.mimetype === "application/zip" ||
|
||||
file.mimetype === "application/x-zip-compressed" ||
|
||||
file.originalname.endsWith(".zip")
|
||||
) {
|
||||
// Zip file containing multiple markdown files
|
||||
const zipResults = await this.importZipFile(workspaceId, userId, file.buffer);
|
||||
results.push(...zipResults);
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
"Invalid file type. Only .md and .zip files are accepted."
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new BadRequestException(
|
||||
`Failed to import file: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
|
||||
const imported = results.filter((r) => r.success).length;
|
||||
const failed = results.filter((r) => !r.success).length;
|
||||
|
||||
return {
|
||||
results,
|
||||
totalFiles: results.length,
|
||||
imported,
|
||||
failed,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single markdown file
|
||||
*/
|
||||
private async importSingleMarkdown(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
filename: string,
|
||||
content: string
|
||||
): Promise<ImportResult> {
|
||||
try {
|
||||
// Parse frontmatter
|
||||
const parsed = matter(content);
|
||||
const frontmatter = parsed.data;
|
||||
const markdownContent = parsed.content.trim();
|
||||
|
||||
if (!markdownContent) {
|
||||
return {
|
||||
filename,
|
||||
success: false,
|
||||
error: "Empty content",
|
||||
};
|
||||
}
|
||||
|
||||
// Build CreateEntryDto from frontmatter and content
|
||||
const parsedStatus = this.parseStatus(frontmatter.status);
|
||||
const parsedVisibility = this.parseVisibility(frontmatter.visibility);
|
||||
const parsedTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : undefined;
|
||||
|
||||
const createDto: CreateEntryDto = {
|
||||
title: frontmatter.title || filename.replace(/\.md$/, ""),
|
||||
content: markdownContent,
|
||||
changeNote: "Imported from markdown file",
|
||||
...(frontmatter.summary && { summary: frontmatter.summary }),
|
||||
...(parsedStatus && { status: parsedStatus }),
|
||||
...(parsedVisibility && { visibility: parsedVisibility }),
|
||||
...(parsedTags && { tags: parsedTags }),
|
||||
};
|
||||
|
||||
// Create the entry
|
||||
const entry = await this.knowledgeService.create(
|
||||
workspaceId,
|
||||
userId,
|
||||
createDto
|
||||
);
|
||||
|
||||
return {
|
||||
filename,
|
||||
success: true,
|
||||
entryId: entry.id,
|
||||
slug: entry.slug,
|
||||
title: entry.title,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
filename,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import entries from a zip file
|
||||
*/
|
||||
private async importZipFile(
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
buffer: Buffer
|
||||
): Promise<ImportResult[]> {
|
||||
const results: ImportResult[] = [];
|
||||
const MAX_FILES = 1000; // Prevent zip bomb attacks
|
||||
const MAX_TOTAL_SIZE = 100 * 1024 * 1024; // 100MB total uncompressed
|
||||
|
||||
try {
|
||||
const zip = new AdmZip(buffer);
|
||||
const zipEntries = zip.getEntries();
|
||||
|
||||
// Security: Check for zip bombs
|
||||
let totalUncompressedSize = 0;
|
||||
let fileCount = 0;
|
||||
|
||||
for (const entry of zipEntries) {
|
||||
if (!entry.isDirectory) {
|
||||
fileCount++;
|
||||
totalUncompressedSize += entry.header.size;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileCount > MAX_FILES) {
|
||||
throw new BadRequestException(
|
||||
`Zip file contains too many files (${fileCount}). Maximum allowed: ${MAX_FILES}`
|
||||
);
|
||||
}
|
||||
|
||||
if (totalUncompressedSize > MAX_TOTAL_SIZE) {
|
||||
throw new BadRequestException(
|
||||
`Zip file is too large when uncompressed (${Math.round(totalUncompressedSize / 1024 / 1024)}MB). Maximum allowed: ${Math.round(MAX_TOTAL_SIZE / 1024 / 1024)}MB`
|
||||
);
|
||||
}
|
||||
|
||||
for (const zipEntry of zipEntries) {
|
||||
// Skip directories and non-markdown files
|
||||
if (zipEntry.isDirectory || !zipEntry.entryName.endsWith(".md")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Security: Prevent path traversal attacks
|
||||
const normalizedPath = zipEntry.entryName.replace(/\\/g, "/");
|
||||
if (
|
||||
normalizedPath.includes("..") ||
|
||||
normalizedPath.startsWith("/") ||
|
||||
normalizedPath.includes("//")
|
||||
) {
|
||||
results.push({
|
||||
filename: zipEntry.entryName,
|
||||
success: false,
|
||||
error: "Invalid file path detected (potential path traversal)",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = zipEntry.getData().toString("utf-8");
|
||||
const result = await this.importSingleMarkdown(
|
||||
workspaceId,
|
||||
userId,
|
||||
zipEntry.entryName,
|
||||
content
|
||||
);
|
||||
results.push(result);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new BadRequestException(
|
||||
`Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export entries as a zip file
|
||||
*/
|
||||
async exportEntries(
|
||||
workspaceId: string,
|
||||
format: ExportFormat,
|
||||
entryIds?: string[]
|
||||
): Promise<{ stream: Readable; filename: string }> {
|
||||
// Fetch entries
|
||||
const entries = await this.fetchEntriesForExport(workspaceId, entryIds);
|
||||
|
||||
if (entries.length === 0) {
|
||||
throw new BadRequestException("No entries found to export");
|
||||
}
|
||||
|
||||
// Create archive
|
||||
const archive = archiver("zip", {
|
||||
zlib: { level: 9 },
|
||||
});
|
||||
|
||||
// Add entries to archive
|
||||
for (const entry of entries) {
|
||||
if (format === "markdown") {
|
||||
const markdown = this.entryToMarkdown(entry);
|
||||
const filename = `${entry.slug}.md`;
|
||||
archive.append(markdown, { name: filename });
|
||||
} else {
|
||||
// JSON format
|
||||
const json = JSON.stringify(entry, null, 2);
|
||||
const filename = `${entry.slug}.json`;
|
||||
archive.append(json, { name: filename });
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize archive
|
||||
archive.finalize();
|
||||
|
||||
// Generate filename
|
||||
const timestamp = new Date().toISOString().split("T")[0];
|
||||
const filename = `knowledge-export-${timestamp}.zip`;
|
||||
|
||||
return {
|
||||
stream: archive,
|
||||
filename,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch entries for export
|
||||
*/
|
||||
private async fetchEntriesForExport(
|
||||
workspaceId: string,
|
||||
entryIds?: string[]
|
||||
): Promise<ExportEntry[]> {
|
||||
const where: Record<string, unknown> = { workspaceId };
|
||||
|
||||
if (entryIds && entryIds.length > 0) {
|
||||
where.id = { in: entryIds };
|
||||
}
|
||||
|
||||
const entries = await this.prisma.knowledgeEntry.findMany({
|
||||
where,
|
||||
include: {
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
title: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
return entries.map((entry) => ({
|
||||
id: entry.id,
|
||||
slug: entry.slug,
|
||||
title: entry.title,
|
||||
content: entry.content,
|
||||
summary: entry.summary,
|
||||
status: entry.status,
|
||||
visibility: entry.visibility,
|
||||
tags: entry.tags.map((et) => et.tag.name),
|
||||
createdAt: entry.createdAt,
|
||||
updatedAt: entry.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert entry to markdown format with frontmatter
|
||||
*/
|
||||
private entryToMarkdown(entry: ExportEntry): string {
|
||||
const frontmatter: Record<string, any> = {
|
||||
title: entry.title,
|
||||
status: entry.status,
|
||||
visibility: entry.visibility,
|
||||
};
|
||||
|
||||
if (entry.summary) {
|
||||
frontmatter.summary = entry.summary;
|
||||
}
|
||||
|
||||
if (entry.tags && entry.tags.length > 0) {
|
||||
frontmatter.tags = entry.tags;
|
||||
}
|
||||
|
||||
frontmatter.createdAt = entry.createdAt.toISOString();
|
||||
frontmatter.updatedAt = entry.updatedAt.toISOString();
|
||||
|
||||
// Build frontmatter string
|
||||
const frontmatterStr = Object.entries(frontmatter)
|
||||
.map(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return `${key}:\n - ${value.join("\n - ")}`;
|
||||
}
|
||||
return `${key}: ${value}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return `---\n${frontmatterStr}\n---\n\n${entry.content}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse status from frontmatter
|
||||
*/
|
||||
private parseStatus(value: unknown): EntryStatus | undefined {
|
||||
if (!value) return undefined;
|
||||
const statusMap: Record<string, EntryStatus> = {
|
||||
DRAFT: EntryStatus.DRAFT,
|
||||
PUBLISHED: EntryStatus.PUBLISHED,
|
||||
ARCHIVED: EntryStatus.ARCHIVED,
|
||||
};
|
||||
return statusMap[String(value).toUpperCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse visibility from frontmatter
|
||||
*/
|
||||
private parseVisibility(value: unknown): Visibility | undefined {
|
||||
if (!value) return undefined;
|
||||
const visibilityMap: Record<string, Visibility> = {
|
||||
PRIVATE: Visibility.PRIVATE,
|
||||
WORKSPACE: Visibility.WORKSPACE,
|
||||
PUBLIC: Visibility.PUBLIC,
|
||||
};
|
||||
return visibilityMap[String(value).toUpperCase()];
|
||||
}
|
||||
}
|
||||
@@ -8,3 +8,4 @@ export { LinkSyncService } from "./link-sync.service";
|
||||
export { SearchService } from "./search.service";
|
||||
export { GraphService } from "./graph.service";
|
||||
export { StatsService } from "./stats.service";
|
||||
export { ImportExportService } from "./import-export.service";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useMemo } from "react";
|
||||
import { EntryStatus } from "@mosaic/shared";
|
||||
import { EntryList } from "@/components/knowledge/EntryList";
|
||||
import { EntryFilters } from "@/components/knowledge/EntryFilters";
|
||||
import { ImportExportActions } from "@/components/knowledge";
|
||||
import { mockEntries, mockTags } from "@/lib/api/knowledge";
|
||||
import Link from "next/link";
|
||||
import { Plus } from "lucide-react";
|
||||
@@ -99,22 +100,35 @@ export default function KnowledgePage() {
|
||||
return (
|
||||
<main className="container mx-auto px-4 py-8 max-w-5xl">
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Knowledge Base</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Documentation, guides, and knowledge entries
|
||||
</p>
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Knowledge Base</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Documentation, guides, and knowledge entries
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Create button */}
|
||||
<Link
|
||||
href="/knowledge/new"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-sm"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
<span>Create Entry</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Create button */}
|
||||
<Link
|
||||
href="/knowledge/new"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-sm"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
<span>Create Entry</span>
|
||||
</Link>
|
||||
{/* Import/Export Actions */}
|
||||
<div className="flex justify-end">
|
||||
<ImportExportActions
|
||||
onImportComplete={() => {
|
||||
// TODO: Refresh the entry list when real API is connected
|
||||
// For now, this would trigger a refetch of the entries
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
|
||||
316
apps/web/src/components/knowledge/ImportExportActions.tsx
Normal file
316
apps/web/src/components/knowledge/ImportExportActions.tsx
Normal file
@@ -0,0 +1,316 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { Upload, Download, Loader2, CheckCircle2, XCircle } from "lucide-react";
|
||||
|
||||
interface ImportResult {
|
||||
filename: string;
|
||||
success: boolean;
|
||||
entryId?: string;
|
||||
slug?: string;
|
||||
title?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface ImportResponse {
|
||||
success: boolean;
|
||||
totalFiles: number;
|
||||
imported: number;
|
||||
failed: number;
|
||||
results: ImportResult[];
|
||||
}
|
||||
|
||||
interface ImportExportActionsProps {
|
||||
selectedEntryIds?: string[];
|
||||
onImportComplete?: () => void;
|
||||
}
|
||||
|
||||
export function ImportExportActions({
|
||||
selectedEntryIds = [],
|
||||
onImportComplete,
|
||||
}: ImportExportActionsProps) {
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const [importResult, setImportResult] = useState<ImportResponse | null>(null);
|
||||
const [showImportDialog, setShowImportDialog] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
/**
|
||||
* Handle import file selection
|
||||
*/
|
||||
const handleImportClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle file upload and import
|
||||
*/
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file type
|
||||
if (!file.name.endsWith(".md") && !file.name.endsWith(".zip")) {
|
||||
alert("Please upload a .md or .zip file");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsImporting(true);
|
||||
setShowImportDialog(true);
|
||||
setImportResult(null);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const response = await fetch("/api/knowledge/import", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.message || "Import failed");
|
||||
}
|
||||
|
||||
const result: ImportResponse = await response.json();
|
||||
setImportResult(result);
|
||||
|
||||
// Notify parent component
|
||||
if (result.imported > 0 && onImportComplete) {
|
||||
onImportComplete();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Import error:", error);
|
||||
alert(error instanceof Error ? error.message : "Failed to import file");
|
||||
setShowImportDialog(false);
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
// Reset file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle export
|
||||
*/
|
||||
const handleExport = async (format: "markdown" | "json" = "markdown") => {
|
||||
setIsExporting(true);
|
||||
|
||||
try {
|
||||
// Build query params
|
||||
const params = new URLSearchParams({
|
||||
format,
|
||||
});
|
||||
|
||||
// Add selected entry IDs if any
|
||||
if (selectedEntryIds.length > 0) {
|
||||
selectedEntryIds.forEach((id) => params.append("entryIds", id));
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/knowledge/export?${params.toString()}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Export failed");
|
||||
}
|
||||
|
||||
// Get filename from Content-Disposition header
|
||||
const contentDisposition = response.headers.get("Content-Disposition");
|
||||
const filenameMatch = contentDisposition?.match(/filename="(.+)"/);
|
||||
const filename = filenameMatch?.[1] || `knowledge-export-${format}.zip`;
|
||||
|
||||
// Download file
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.error("Export error:", error);
|
||||
alert("Failed to export entries");
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close import dialog
|
||||
*/
|
||||
const handleCloseImportDialog = () => {
|
||||
setShowImportDialog(false);
|
||||
setImportResult(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Import Button */}
|
||||
<button
|
||||
onClick={handleImportClick}
|
||||
disabled={isImporting}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Import entries from .md or .zip file"
|
||||
>
|
||||
{isImporting ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Upload className="w-5 h-5" />
|
||||
)}
|
||||
<span>Import</span>
|
||||
</button>
|
||||
|
||||
{/* Export Dropdown */}
|
||||
<div className="relative group">
|
||||
<button
|
||||
disabled={isExporting}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Export entries"
|
||||
>
|
||||
{isExporting ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Download className="w-5 h-5" />
|
||||
)}
|
||||
<span>Export</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
<div className="absolute right-0 mt-1 w-48 bg-white rounded-lg shadow-lg border border-gray-200 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-10">
|
||||
<div className="py-1">
|
||||
<button
|
||||
onClick={() => handleExport("markdown")}
|
||||
className="w-full text-left px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
|
||||
>
|
||||
Export as Markdown
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleExport("json")}
|
||||
className="w-full text-left px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
|
||||
>
|
||||
Export as JSON
|
||||
</button>
|
||||
{selectedEntryIds.length > 0 && (
|
||||
<div className="border-t border-gray-200 mt-1 pt-1 px-4 py-2 text-xs text-gray-500">
|
||||
{selectedEntryIds.length} selected
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hidden File Input */}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".md,.zip"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
{/* Import Result Dialog */}
|
||||
{showImportDialog && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{isImporting ? "Importing..." : "Import Results"}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-6 py-4 overflow-y-auto flex-1">
|
||||
{isImporting && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
|
||||
<span className="ml-3 text-gray-600">Processing file...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{importResult && (
|
||||
<div>
|
||||
{/* Summary */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-6">
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<div className="text-sm text-gray-600">Total Files</div>
|
||||
<div className="text-2xl font-semibold text-gray-900">
|
||||
{importResult.totalFiles}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-green-50 rounded-lg p-4">
|
||||
<div className="text-sm text-green-600">Imported</div>
|
||||
<div className="text-2xl font-semibold text-green-700">
|
||||
{importResult.imported}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-red-50 rounded-lg p-4">
|
||||
<div className="text-sm text-red-600">Failed</div>
|
||||
<div className="text-2xl font-semibold text-red-700">
|
||||
{importResult.failed}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results List */}
|
||||
{importResult.results.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-gray-900 mb-2">Details</h3>
|
||||
{importResult.results.map((result, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg ${
|
||||
result.success ? "bg-green-50" : "bg-red-50"
|
||||
}`}
|
||||
>
|
||||
{result.success ? (
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
) : (
|
||||
<XCircle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm text-gray-900 truncate">
|
||||
{result.title || result.filename}
|
||||
</div>
|
||||
{result.success ? (
|
||||
<div className="text-xs text-gray-600">
|
||||
{result.slug && `Slug: ${result.slug}`}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-red-600">{result.error}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
{!isImporting && (
|
||||
<div className="px-6 py-4 border-t border-gray-200 flex justify-end">
|
||||
<button
|
||||
onClick={handleCloseImportDialog}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,8 @@
|
||||
* Knowledge module components
|
||||
*/
|
||||
|
||||
export { BacklinksList } from "./BacklinksList";
|
||||
export { EntryViewer } from "./EntryViewer";
|
||||
export { EntryEditor } from "./EntryEditor";
|
||||
export { EntryMetadata } from "./EntryMetadata";
|
||||
export { VersionHistory } from "./VersionHistory";
|
||||
export { StatsDashboard } from "./StatsDashboard";
|
||||
export { EntryGraphViewer } from "./EntryGraphViewer";
|
||||
export { ImportExportActions } from "./ImportExportActions";
|
||||
|
||||
392
pnpm-lock.yaml
generated
392
pnpm-lock.yaml
generated
@@ -68,6 +68,12 @@ importers:
|
||||
'@types/marked':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
archiver:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
better-auth:
|
||||
specifier: ^1.4.17
|
||||
version: 1.4.17(@prisma/client@6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3))(better-sqlite3@12.6.2)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18(@types/node@22.19.7)(jiti@2.6.1)(jsdom@26.1.0)(terser@5.46.0)(tsx@4.21.0))
|
||||
@@ -77,9 +83,15 @@ importers:
|
||||
class-validator:
|
||||
specifier: ^0.14.3
|
||||
version: 0.14.3
|
||||
gray-matter:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
highlight.js:
|
||||
specifier: ^11.11.1
|
||||
version: 11.11.1
|
||||
ioredis:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
marked:
|
||||
specifier: ^17.0.1
|
||||
version: 17.0.1
|
||||
@@ -126,15 +138,21 @@ importers:
|
||||
'@swc/core':
|
||||
specifier: ^1.10.18
|
||||
version: 1.15.11
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7
|
||||
'@types/archiver':
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
'@types/express':
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.6
|
||||
'@types/highlight.js':
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0
|
||||
'@types/ioredis':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
'@types/multer':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@types/node':
|
||||
specifier: ^22.13.4
|
||||
version: 22.19.7
|
||||
@@ -1695,6 +1713,12 @@ packages:
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
'@types/adm-zip@0.5.7':
|
||||
resolution: {integrity: sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==}
|
||||
|
||||
'@types/archiver@7.0.0':
|
||||
resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
@@ -1843,10 +1867,6 @@ packages:
|
||||
'@types/http-errors@2.0.5':
|
||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||
|
||||
'@types/ioredis@5.0.0':
|
||||
resolution: {integrity: sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g==}
|
||||
deprecated: This is a stub types definition. ioredis provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
@@ -1854,6 +1874,9 @@ packages:
|
||||
resolution: {integrity: sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA==}
|
||||
deprecated: This is a stub types definition. marked provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/multer@2.0.0':
|
||||
resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==}
|
||||
|
||||
'@types/node@22.19.7':
|
||||
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
|
||||
|
||||
@@ -1878,6 +1901,9 @@ packages:
|
||||
'@types/react@19.2.10':
|
||||
resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==}
|
||||
|
||||
'@types/readdir-glob@1.1.5':
|
||||
resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==}
|
||||
|
||||
'@types/sanitize-html@2.16.0':
|
||||
resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==}
|
||||
|
||||
@@ -2094,6 +2120,10 @@ packages:
|
||||
'@xyflow/system@0.0.74':
|
||||
resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==}
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
|
||||
accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2118,6 +2148,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adm-zip@0.5.16:
|
||||
resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -2185,6 +2219,17 @@ packages:
|
||||
append-field@1.0.0:
|
||||
resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
|
||||
|
||||
archiver-utils@5.0.2:
|
||||
resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
archiver@7.0.1:
|
||||
resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
@@ -2205,9 +2250,28 @@ packages:
|
||||
ast-v8-to-istanbul@0.3.10:
|
||||
resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==}
|
||||
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
b4a@1.7.3:
|
||||
resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==}
|
||||
peerDependencies:
|
||||
react-native-b4a: '*'
|
||||
peerDependenciesMeta:
|
||||
react-native-b4a:
|
||||
optional: true
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
bare-events@2.8.2:
|
||||
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
|
||||
peerDependencies:
|
||||
bare-abort-controller: '*'
|
||||
peerDependenciesMeta:
|
||||
bare-abort-controller:
|
||||
optional: true
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@@ -2314,12 +2378,19 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-crc32@1.0.0:
|
||||
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2492,6 +2563,10 @@ packages:
|
||||
resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
compress-commons@6.0.2:
|
||||
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -2550,6 +2625,15 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
|
||||
crc32-stream@6.0.0:
|
||||
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -3105,6 +3189,13 @@ packages:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
events-universal@1.0.1:
|
||||
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
@@ -3124,6 +3215,10 @@ packages:
|
||||
exsolve@1.0.8:
|
||||
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
fast-check@3.23.2:
|
||||
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -3137,6 +3232,9 @@ packages:
|
||||
fast-equals@4.0.3:
|
||||
resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==}
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
|
||||
@@ -3272,6 +3370,10 @@ packages:
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
gray-matter@4.0.3:
|
||||
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
hachure-fill@0.5.2:
|
||||
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
||||
|
||||
@@ -3373,6 +3475,10 @@ packages:
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
is-extendable@0.1.1:
|
||||
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3404,6 +3510,10 @@ packages:
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
is-stream@2.0.1:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3412,6 +3522,9 @@ packages:
|
||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
@@ -3455,6 +3568,10 @@ packages:
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
@@ -3509,6 +3626,10 @@ packages:
|
||||
khroma@2.1.0:
|
||||
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
|
||||
|
||||
kind-of@6.0.3:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
kleur@3.0.3:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3527,6 +3648,10 @@ packages:
|
||||
layout-base@2.0.1:
|
||||
resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
|
||||
|
||||
lazystream@1.0.1:
|
||||
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
|
||||
engines: {node: '>= 0.6.3'}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -3705,6 +3830,10 @@ packages:
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
minimatch@5.1.6:
|
||||
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -3800,6 +3929,10 @@ packages:
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
nwsapi@2.2.23:
|
||||
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
|
||||
|
||||
@@ -4040,6 +4173,13 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
process@0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -4120,10 +4260,20 @@ packages:
|
||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
readable-stream@4.7.0:
|
||||
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
readdir-glob@1.1.3:
|
||||
resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
@@ -4202,6 +4352,9 @@ packages:
|
||||
rxjs@7.8.2:
|
||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
@@ -4226,6 +4379,10 @@ packages:
|
||||
resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
section-matter@1.0.0:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
@@ -4337,6 +4494,9 @@ packages:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
@@ -4354,6 +4514,9 @@ packages:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
streamx@2.23.0:
|
||||
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4362,6 +4525,9 @@ packages:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
@@ -4373,6 +4539,10 @@ packages:
|
||||
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-bom-string@1.0.0:
|
||||
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-bom@3.0.0:
|
||||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4442,6 +4612,9 @@ packages:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
|
||||
terser-webpack-plugin@5.3.16:
|
||||
resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
@@ -4467,6 +4640,9 @@ packages:
|
||||
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
text-decoder@1.2.3:
|
||||
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -4944,6 +5120,10 @@ packages:
|
||||
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
zip-stream@6.0.1:
|
||||
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
zod@4.3.6:
|
||||
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||
|
||||
@@ -6300,6 +6480,14 @@ snapshots:
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@types/adm-zip@0.5.7':
|
||||
dependencies:
|
||||
'@types/node': 22.19.7
|
||||
|
||||
'@types/archiver@7.0.0':
|
||||
dependencies:
|
||||
'@types/readdir-glob': 1.1.5
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
@@ -6493,18 +6681,16 @@ snapshots:
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
'@types/ioredis@5.0.0':
|
||||
dependencies:
|
||||
ioredis: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/marked@6.0.0':
|
||||
dependencies:
|
||||
marked: 17.0.1
|
||||
|
||||
'@types/multer@2.0.0':
|
||||
dependencies:
|
||||
'@types/express': 5.0.6
|
||||
|
||||
'@types/node@22.19.7':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -6534,6 +6720,10 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/readdir-glob@1.1.5':
|
||||
dependencies:
|
||||
'@types/node': 22.19.7
|
||||
|
||||
'@types/sanitize-html@2.16.0':
|
||||
dependencies:
|
||||
htmlparser2: 8.0.2
|
||||
@@ -6886,6 +7076,10 @@ snapshots:
|
||||
d3-selection: 3.0.0
|
||||
d3-zoom: 3.0.0
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
@@ -6906,6 +7100,8 @@ snapshots:
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
adm-zip@0.5.16: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.17.1):
|
||||
@@ -6957,6 +7153,33 @@ snapshots:
|
||||
|
||||
append-field@1.0.0: {}
|
||||
|
||||
archiver-utils@5.0.2:
|
||||
dependencies:
|
||||
glob: 10.5.0
|
||||
graceful-fs: 4.2.11
|
||||
is-stream: 2.0.1
|
||||
lazystream: 1.0.1
|
||||
lodash: 4.17.23
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 4.7.0
|
||||
|
||||
archiver@7.0.1:
|
||||
dependencies:
|
||||
archiver-utils: 5.0.2
|
||||
async: 3.2.6
|
||||
buffer-crc32: 1.0.0
|
||||
readable-stream: 4.7.0
|
||||
readdir-glob: 1.1.3
|
||||
tar-stream: 3.1.7
|
||||
zip-stream: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-query@5.3.0:
|
||||
@@ -6975,8 +7198,14 @@ snapshots:
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 9.0.1
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
b4a@1.7.3: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bare-events@2.8.2: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
base64id@2.0.0: {}
|
||||
@@ -7113,6 +7342,8 @@ snapshots:
|
||||
node-releases: 2.0.27
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||
|
||||
buffer-crc32@1.0.0: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
@@ -7120,6 +7351,11 @@ snapshots:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
buffer@6.0.3:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bundle-name@4.1.0:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
@@ -7296,6 +7532,14 @@ snapshots:
|
||||
core-util-is: 1.0.3
|
||||
esprima: 4.0.1
|
||||
|
||||
compress-commons@6.0.2:
|
||||
dependencies:
|
||||
crc-32: 1.2.2
|
||||
crc32-stream: 6.0.0
|
||||
is-stream: 2.0.1
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 4.7.0
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
@@ -7345,6 +7589,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
crc32-stream@6.0.0:
|
||||
dependencies:
|
||||
crc-32: 1.2.2
|
||||
readable-stream: 4.7.0
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -7861,6 +8112,14 @@ snapshots:
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
events-universal@1.0.1:
|
||||
dependencies:
|
||||
bare-events: 2.8.2
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
expand-template@2.0.3: {}
|
||||
@@ -7902,6 +8161,10 @@ snapshots:
|
||||
|
||||
exsolve@1.0.8: {}
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
dependencies:
|
||||
is-extendable: 0.1.1
|
||||
|
||||
fast-check@3.23.2:
|
||||
dependencies:
|
||||
pure-rand: 6.1.0
|
||||
@@ -7912,6 +8175,8 @@ snapshots:
|
||||
|
||||
fast-equals@4.0.3: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
fast-levenshtein@2.0.6: {}
|
||||
@@ -8067,6 +8332,13 @@ snapshots:
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
gray-matter@4.0.3:
|
||||
dependencies:
|
||||
js-yaml: 3.14.2
|
||||
kind-of: 6.0.3
|
||||
section-matter: 1.0.0
|
||||
strip-bom-string: 1.0.0
|
||||
|
||||
hachure-fill@0.5.2: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -8165,6 +8437,8 @@ snapshots:
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extendable@0.1.1: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
@@ -8185,12 +8459,16 @@ snapshots:
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-unicode-supported@0.1.0: {}
|
||||
|
||||
is-wsl@3.1.0:
|
||||
dependencies:
|
||||
is-inside-container: 1.0.0
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
@@ -8236,6 +8514,11 @@ snapshots:
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
@@ -8299,6 +8582,8 @@ snapshots:
|
||||
|
||||
khroma@2.1.0: {}
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
kysely@0.28.10: {}
|
||||
@@ -8315,6 +8600,10 @@ snapshots:
|
||||
|
||||
layout-base@2.0.1: {}
|
||||
|
||||
lazystream@1.0.1:
|
||||
dependencies:
|
||||
readable-stream: 2.3.8
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -8475,6 +8764,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@5.1.6:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
@@ -8562,6 +8855,8 @@ snapshots:
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
nwsapi@2.2.23: {}
|
||||
|
||||
nypm@0.6.4:
|
||||
@@ -8805,6 +9100,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
process@0.11.10: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
@@ -8897,12 +9196,34 @@ snapshots:
|
||||
|
||||
react@19.2.4: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
inherits: 2.0.4
|
||||
isarray: 1.0.0
|
||||
process-nextick-args: 2.0.1
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@4.7.0:
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
buffer: 6.0.3
|
||||
events: 3.3.0
|
||||
process: 0.11.10
|
||||
string_decoder: 1.3.0
|
||||
|
||||
readdir-glob@1.1.3:
|
||||
dependencies:
|
||||
minimatch: 5.1.6
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
readdirp@5.0.0: {}
|
||||
@@ -9001,6 +9322,8 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
safe-buffer@5.1.2: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
@@ -9033,6 +9356,11 @@ snapshots:
|
||||
ajv-formats: 2.1.1(ajv@8.17.1)
|
||||
ajv-keywords: 5.1.0(ajv@8.17.1)
|
||||
|
||||
section-matter@1.0.0:
|
||||
dependencies:
|
||||
extend-shallow: 2.0.1
|
||||
kind-of: 6.0.3
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
semver@7.7.3: {}
|
||||
@@ -9208,6 +9536,8 @@ snapshots:
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
standard-as-callback@2.1.0: {}
|
||||
@@ -9218,6 +9548,15 @@ snapshots:
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
||||
streamx@2.23.0:
|
||||
dependencies:
|
||||
events-universal: 1.0.1
|
||||
fast-fifo: 1.3.2
|
||||
text-decoder: 1.2.3
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -9230,6 +9569,10 @@ snapshots:
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -9242,6 +9585,8 @@ snapshots:
|
||||
dependencies:
|
||||
ansi-regex: 6.2.2
|
||||
|
||||
strip-bom-string@1.0.0: {}
|
||||
|
||||
strip-bom@3.0.0: {}
|
||||
|
||||
strip-indent@3.0.0:
|
||||
@@ -9302,6 +9647,15 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
tar-stream@3.1.7:
|
||||
dependencies:
|
||||
b4a: 1.7.3
|
||||
fast-fifo: 1.3.2
|
||||
streamx: 2.23.0
|
||||
transitivePeerDependencies:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
terser-webpack-plugin@5.3.16(@swc/core@1.15.11)(webpack@5.104.1(@swc/core@1.15.11)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -9326,6 +9680,12 @@ snapshots:
|
||||
glob: 10.5.0
|
||||
minimatch: 9.0.5
|
||||
|
||||
text-decoder@1.2.3:
|
||||
dependencies:
|
||||
b4a: 1.7.3
|
||||
transitivePeerDependencies:
|
||||
- react-native-b4a
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
@@ -9768,6 +10128,12 @@ snapshots:
|
||||
|
||||
yoctocolors@2.1.2: {}
|
||||
|
||||
zip-stream@6.0.1:
|
||||
dependencies:
|
||||
archiver-utils: 5.0.2
|
||||
compress-commons: 6.0.2
|
||||
readable-stream: 4.7.0
|
||||
|
||||
zod@4.3.6: {}
|
||||
|
||||
zustand@4.5.7(@types/react@19.2.10)(react@19.2.4):
|
||||
|
||||
Reference in New Issue
Block a user