excel2json init
This commit is contained in:
16
.claude/settings.local.json
Normal file
16
.claude/settings.local.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm install:*)",
|
||||
"mcp__svelte__svelte-autofixer",
|
||||
"Bash(npm run check:*)",
|
||||
"Bash(npx vitest:*)",
|
||||
"Bash(node -e:*)",
|
||||
"Bash(npm run build:*)"
|
||||
]
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"enabledMcpjsonServers": [
|
||||
"svelte"
|
||||
]
|
||||
}
|
||||
7
.cursor/mcp.json
Normal file
7
.cursor/mcp.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
.gemini/settings.json
Normal file
8
.gemini/settings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
8
.mcp.json
Normal file
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
.vscode/mcp.json
vendored
Normal file
7
.vscode/mcp.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"servers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
}
|
||||
}
|
||||
23
AGENTS.md
Normal file
23
AGENTS.md
Normal file
@@ -0,0 +1,23 @@
|
||||
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available MCP Tools:
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns issues and suggestions.
|
||||
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||
|
||||
### 4. playground-link
|
||||
|
||||
Generates a Svelte Playground link with the provided code.
|
||||
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||
48
CLAUDE.md
Normal file
48
CLAUDE.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**excel2json** — a SvelteKit web application for converting Excel files to JSON. Built with Svelte 5, SvelteKit 2, TypeScript, and Tailwind CSS 4.
|
||||
|
||||
## Commands
|
||||
|
||||
- `npm run dev` — start dev server
|
||||
- `npm run build` — production build
|
||||
- `npm run preview` — preview production build
|
||||
- `npm run check` — type-check with svelte-check
|
||||
- `npm run test` — run all tests once
|
||||
- `npm run test:unit` — run tests in watch mode
|
||||
- `npm run test:unit -- --run --testNamePattern="pattern"` — run a single test by name
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Framework**: SvelteKit 2 with Svelte 5 (runes API: `$state`, `$props`, `$derived`, etc.)
|
||||
- **Styling**: Tailwind CSS 4 with `@tailwindcss/forms` and `@tailwindcss/typography` plugins, configured via `src/routes/layout.css`
|
||||
- **Adapter**: `@sveltejs/adapter-auto`
|
||||
- **TypeScript**: strict mode enabled
|
||||
|
||||
### Testing
|
||||
|
||||
Two Vitest project configurations in `vite.config.ts`:
|
||||
|
||||
- **`client`** — browser tests using Playwright (headless Chromium). Files matching `src/**/*.svelte.{test,spec}.{js,ts}`. Uses `vitest-browser-svelte` for component rendering.
|
||||
- **`server`** — Node.js unit tests. Files matching `src/**/*.{test,spec}.{js,ts}` (excluding `.svelte.` test files).
|
||||
|
||||
All tests require assertions (`expect.requireAssertions: true`).
|
||||
|
||||
### Key Conventions
|
||||
|
||||
- Svelte components use `lang="ts"` in script tags
|
||||
- Shared library code goes in `src/lib/` (aliased as `$lib`)
|
||||
- Use Svelte 5 runes syntax, not legacy Svelte 4 patterns
|
||||
|
||||
## Svelte MCP Server
|
||||
|
||||
A Svelte MCP server is available for Svelte 5 / SvelteKit documentation lookup and code validation. When writing Svelte code:
|
||||
|
||||
1. Use `list-sections` first to discover relevant documentation
|
||||
2. Use `get-documentation` to fetch needed sections
|
||||
3. Use `svelte-autofixer` to validate Svelte code before finalizing — keep calling until no issues remain
|
||||
4. Offer `playground-link` only if code was NOT written to project files
|
||||
126
DEMAND.md
Normal file
126
DEMAND.md
Normal file
@@ -0,0 +1,126 @@
|
||||
项目需求文档 (PRD): Excel 转 JSON 可视化映射工具
|
||||
1. 项目概述
|
||||
|
||||
我们需要开发一个基于 Svelte 的单页应用。该应用允许用户上传 Excel 文件,在左侧预览数据,在右侧实时预览转换后的 JSON 数据。核心功能是用户可以自定义“Excel列”到“JSON字段”的映射规则、处理空值逻辑以及格式化特定数据类型(如日期),并支持将这些配置导出为模板,以便下次复用。
|
||||
2. 技术栈要求
|
||||
|
||||
框架: Svelte (推荐使用 Svelte 5 或 SvelteKit) + TypeScript
|
||||
|
||||
样式: TailwindCSS (用于快速构建左右分栏布局)
|
||||
|
||||
Excel 处理: xlsx (SheetJS) 或类似的轻量级库
|
||||
|
||||
图标库: Lucide-svelte (可选)
|
||||
|
||||
3. 界面布局 (UI Layout)
|
||||
|
||||
页面主要分为 顶部工具栏 和 主体内容区。
|
||||
|
||||
顶部工具栏 (Header):
|
||||
|
||||
文件上传按钮 (支持拖拽上传 Excel)。
|
||||
|
||||
模板操作区:[导入配置模板]、[导出当前配置]。
|
||||
|
||||
全局操作:[下载 JSON]、[复制 JSON]。
|
||||
|
||||
主体内容区 (Main Split View):
|
||||
|
||||
左侧 (Source Panel - 50%): 显示 Excel 解析后的表格数据。
|
||||
|
||||
关键交互: 表头应包含“设置”功能。用户点击表头或表头旁边的图标,可以弹出/展开该列的映射配置面板。
|
||||
|
||||
右侧 (Target Panel - 50%): 显示转换后的 JSON 代码预览(支持语法高亮)。
|
||||
|
||||
4. 核心功能细节
|
||||
4.1 Excel 导入与展示
|
||||
|
||||
支持 .xlsx, .xls, .csv 格式。
|
||||
|
||||
读取 Excel 的第一行作为默认表头(Key)。
|
||||
|
||||
数据以表格形式展示在左侧。
|
||||
|
||||
4.2 字段映射配置 (Mapping Logic)
|
||||
|
||||
这是本应用的核心。每一列都需要一个配置对象,包含以下属性:
|
||||
|
||||
Original Header (源字段): Excel 原始表头名称 (只读)。
|
||||
|
||||
Target Key (目标字段): 映射到 JSON 中的 Key 名称 (用户可修改)。
|
||||
|
||||
示例: Excel 中是 "姓名",用户输入 "userName",生成的 JSON 为 {"userName": "..."}。
|
||||
|
||||
Data Type (数据类型):
|
||||
|
||||
String (默认)
|
||||
|
||||
Number
|
||||
|
||||
Boolean
|
||||
|
||||
Date
|
||||
|
||||
Format Rules (格式化规则 - 仅针对特定类型):
|
||||
|
||||
如果是 Date 类型,提供格式化选项 (如 YYYY-MM-DD, YYYY/MM/DD HH:mm, Unix Timestamp)。需要引入 dayjs 或类似库处理。
|
||||
|
||||
Null Handling (空值处理):
|
||||
|
||||
开关选项:Exclude if Empty (如果该单元格为空,则在生成的 JSON 对象中完全移除该 Key)。
|
||||
|
||||
默认值:如果未勾选“移除”,可设置一个默认值 (Default Value)。
|
||||
|
||||
4.3 JSON 实时预览
|
||||
|
||||
当用户修改映射配置(如修改 Key 名称、切换日期格式、改变空值策略)时,右侧的 JSON 预览应 实时 (Reactive) 更新。
|
||||
|
||||
4.4 模板系统 (Configuration Persistence)
|
||||
|
||||
导出模板: 将当前的映射规则数组导出为 .json 文件。
|
||||
|
||||
数据结构示例:
|
||||
JSON
|
||||
|
||||
[
|
||||
{ "source": "A", "target": "AAA", "type": "string", "excludeIfEmpty": false },
|
||||
{ "source": "入职日期", "target": "joinDate", "type": "date", "format": "YYYY-MM-DD" }
|
||||
]
|
||||
|
||||
导入模板: 上传上述格式的 JSON 文件,应用自动匹配当前 Excel 的表头。如果 Excel 包含模板中定义的 source 字段,则自动应用对应的规则。
|
||||
|
||||
5. 交互流程 (User Story)
|
||||
|
||||
用户打开页面,拖入 staff_data.xlsx。
|
||||
|
||||
左侧显示表格。用户发现“出生日期”这一列解析出来是数字(Excel 时间戳)。
|
||||
|
||||
用户点击“出生日期”列的设置,将类型改为 Date,格式选择 YYYY-MM-DD。
|
||||
|
||||
用户发现有一列“备注”很多是空的,点击设置,勾选 Exclude if Empty。
|
||||
|
||||
用户将“姓名”列的 Target Key 改为 full_name。
|
||||
|
||||
右侧 JSON 实时变成了期望的格式。
|
||||
|
||||
用户点击“导出配置”,保存为 staff_mapping.json。
|
||||
|
||||
下周,用户上传新的 Excel,并点击“导入配置”选择 staff_mapping.json,所有规则自动应用,直接复制右侧 JSON。
|
||||
|
||||
请执行以下任务:
|
||||
|
||||
数据结构设计: 定义 MappingConfig 和 RowData 的 TypeScript 接口。
|
||||
|
||||
核心逻辑实现: 编写一个 convertData 函数,根据映射配置将 Excel 原始数据转换为最终 JSON。
|
||||
|
||||
组件编写:
|
||||
|
||||
App.svelte: 主布局和状态管理。
|
||||
|
||||
ExcelTable.svelte: 左侧表格,包含列头配置 UI。
|
||||
|
||||
JsonPreview.svelte: 右侧展示。
|
||||
|
||||
请确保代码不仅能运行,而且具有良好的错误处理(例如文件解析失败)。
|
||||
|
||||
支持嵌套对象: 如果 Target Key 包含点号(例如 user.address.city),生成的 JSON 应当自动构建对应的嵌套对象结构,而不是生成一个带点的字符串键名。
|
||||
23
GEMINI.md
Normal file
23
GEMINI.md
Normal file
@@ -0,0 +1,23 @@
|
||||
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available MCP Tools:
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns issues and suggestions.
|
||||
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||
|
||||
### 4. playground-link
|
||||
|
||||
Generates a Svelte Playground link with the provided code.
|
||||
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# sv
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
To recreate this project with the same configuration:
|
||||
|
||||
```sh
|
||||
# recreate this project
|
||||
npx sv create --template minimal --types ts --add vitest="usages:unit,component" tailwindcss="plugins:typography,forms" devtools-json mcp="ide:claude-code,vscode,other,gemini,cursor,opencode+setup:remote" --install yarn .
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
9
opencode.json
Normal file
9
opencode.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"svelte": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
1738
package-lock.json
generated
Normal file
1738
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "excel2json",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"playwright": "^1.58.1",
|
||||
"svelte": "^5.49.2",
|
||||
"svelte-check": "^4.3.6",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-devtools-json": "^1.0.0",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-browser-svelte": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.19",
|
||||
"xlsx": "^0.18.5"
|
||||
}
|
||||
}
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
11
src/app.html
Normal file
11
src/app.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
7
src/demo.spec.ts
Normal file
7
src/demo.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
1
src/lib/assets/favicon.svg
Normal file
1
src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
107
src/lib/components/ColumnConfig.svelte
Normal file
107
src/lib/components/ColumnConfig.svelte
Normal file
@@ -0,0 +1,107 @@
|
||||
<script lang="ts">
|
||||
import type { MappingConfig, DataType, DateFormat } from '$lib/types.js';
|
||||
|
||||
let { config = $bindable(), onclose }: { config: MappingConfig; onclose: () => void } = $props();
|
||||
|
||||
const dataTypes: { value: DataType; label: string }[] = [
|
||||
{ value: 'string', label: '字符串 (String)' },
|
||||
{ value: 'number', label: '数字 (Number)' },
|
||||
{ value: 'boolean', label: '布尔 (Boolean)' },
|
||||
{ value: 'date', label: '日期 (Date)' }
|
||||
];
|
||||
|
||||
const dateFormats: { value: DateFormat; label: string }[] = [
|
||||
{ value: 'YYYY-MM-DD', label: 'YYYY-MM-DD' },
|
||||
{ value: 'YYYY/MM/DD', label: 'YYYY/MM/DD' },
|
||||
{ value: 'YYYY-MM-DD HH:mm', label: 'YYYY-MM-DD HH:mm' },
|
||||
{ value: 'YYYY/MM/DD HH:mm', label: 'YYYY/MM/DD HH:mm' },
|
||||
{ value: 'YYYY-MM-DD HH:mm:ss', label: 'YYYY-MM-DD HH:mm:ss' },
|
||||
{ value: 'timestamp', label: 'Unix Timestamp' }
|
||||
];
|
||||
|
||||
function onTypeChange(e: Event) {
|
||||
const select = e.target as HTMLSelectElement;
|
||||
config.type = select.value as DataType;
|
||||
if (config.type === 'date' && !config.format) {
|
||||
config.format = 'YYYY-MM-DD';
|
||||
}
|
||||
if (config.type !== 'date') {
|
||||
config.format = undefined;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="absolute top-full left-0 z-50 mt-1 w-72 rounded-lg border border-gray-200 bg-white p-4 shadow-xl">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h4 class="text-sm font-semibold text-gray-700">列配置</h4>
|
||||
<button onclick={onclose} aria-label="关闭配置" class="text-gray-400 hover:text-gray-600 cursor-pointer">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Source header (read-only) -->
|
||||
<div>
|
||||
<label for="source-{config.source}" class="mb-1 block text-xs font-medium text-gray-500">源字段</label>
|
||||
<input id="source-{config.source}" type="text" value={config.source} disabled
|
||||
class="w-full rounded border border-gray-200 bg-gray-50 px-2 py-1.5 text-sm text-gray-500" />
|
||||
</div>
|
||||
|
||||
<!-- Target key -->
|
||||
<div>
|
||||
<label for="target-{config.source}" class="mb-1 block text-xs font-medium text-gray-500">目标字段 (JSON Key)</label>
|
||||
<input id="target-{config.source}" type="text" bind:value={config.target}
|
||||
class="w-full rounded border border-gray-300 px-2 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500" />
|
||||
</div>
|
||||
|
||||
<!-- Data type -->
|
||||
<div>
|
||||
<label for="type-{config.source}" class="mb-1 block text-xs font-medium text-gray-500">数据类型</label>
|
||||
<select id="type-{config.source}" value={config.type} onchange={onTypeChange}
|
||||
class="w-full rounded border border-gray-300 px-2 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500">
|
||||
{#each dataTypes as dt (dt.value)}
|
||||
<option value={dt.value}>{dt.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Date format (only for date type) -->
|
||||
{#if config.type === 'date'}
|
||||
<div>
|
||||
<label for="format-{config.source}" class="mb-1 block text-xs font-medium text-gray-500">日期格式</label>
|
||||
<select id="format-{config.source}" bind:value={config.format}
|
||||
class="w-full rounded border border-gray-300 px-2 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500">
|
||||
{#each dateFormats as df (df.value)}
|
||||
<option value={df.value}>{df.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Exclude if empty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" id="exclude-empty-{config.source}" bind:checked={config.excludeIfEmpty}
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||
<label for="exclude-empty-{config.source}" class="text-sm text-gray-600">空值时移除该字段</label>
|
||||
</div>
|
||||
|
||||
<!-- Default value -->
|
||||
{#if !config.excludeIfEmpty}
|
||||
<div>
|
||||
<label for="default-{config.source}" class="mb-1 block text-xs font-medium text-gray-500">默认值 (空值时)</label>
|
||||
<input id="default-{config.source}" type="text" bind:value={config.defaultValue}
|
||||
class="w-full rounded border border-gray-300 px-2 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
||||
placeholder="留空则为 null" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Enable/disable column -->
|
||||
<div class="flex items-center gap-2 border-t border-gray-100 pt-3">
|
||||
<input type="checkbox" id="enabled-{config.source}" bind:checked={config.enabled}
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||
<label for="enabled-{config.source}" class="text-sm text-gray-600">包含此列到输出</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
94
src/lib/components/ExcelTable.svelte
Normal file
94
src/lib/components/ExcelTable.svelte
Normal file
@@ -0,0 +1,94 @@
|
||||
<script lang="ts">
|
||||
import type { MappingConfig, RowData } from '$lib/types.js';
|
||||
import ColumnConfig from './ColumnConfig.svelte';
|
||||
|
||||
let {
|
||||
headers,
|
||||
rows,
|
||||
mappings = $bindable()
|
||||
}: {
|
||||
headers: string[];
|
||||
rows: RowData[];
|
||||
mappings: MappingConfig[];
|
||||
} = $props();
|
||||
|
||||
let activeConfigIndex = $state<number | null>(null);
|
||||
const maxPreviewRows = 100;
|
||||
|
||||
function toggleConfig(index: number) {
|
||||
activeConfigIndex = activeConfigIndex === index ? null : index;
|
||||
}
|
||||
|
||||
function displayValue(value: unknown): string {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
if (value instanceof Date) {
|
||||
const y = value.getFullYear();
|
||||
const m = String(value.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(value.getDate()).padStart(2, '0');
|
||||
const H = String(value.getHours()).padStart(2, '0');
|
||||
const M = String(value.getMinutes()).padStart(2, '0');
|
||||
const S = String(value.getSeconds()).padStart(2, '0');
|
||||
if (H === '00' && M === '00' && S === '00') return `${y}-${m}-${d}`;
|
||||
return `${y}-${m}-${d} ${H}:${M}:${S}`;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="flex-shrink-0 border-b border-gray-200 bg-gray-50 px-4 py-2">
|
||||
<h3 class="text-sm font-semibold text-gray-700">
|
||||
Excel 数据
|
||||
<span class="ml-2 text-xs font-normal text-gray-400">
|
||||
{rows.length} 行 × {headers.length} 列
|
||||
{#if rows.length > maxPreviewRows}
|
||||
(显示前 {maxPreviewRows} 行)
|
||||
{/if}
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="sticky top-0 z-10 bg-gray-50">
|
||||
<tr>
|
||||
{#each headers as header, i (header)}
|
||||
<th class="relative border-b border-r border-gray-200 px-3 py-2 text-left font-medium text-gray-600">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="truncate" title={header}>{header}</span>
|
||||
<button
|
||||
onclick={() => toggleConfig(i)}
|
||||
class="ml-auto flex-shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-200 hover:text-gray-600 cursor-pointer"
|
||||
title="配置此列"
|
||||
>
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
{#if mappings[i] && mappings[i].target !== mappings[i].source}
|
||||
<span class="text-xs text-blue-500" title="映射为: {mappings[i].target}">→ {mappings[i].target}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if activeConfigIndex === i && mappings[i]}
|
||||
<ColumnConfig bind:config={mappings[i]} onclose={() => (activeConfigIndex = null)} />
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows.slice(0, maxPreviewRows) as row, rowIdx (rowIdx)}
|
||||
<tr class={rowIdx % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'}>
|
||||
{#each headers as header (header)}
|
||||
<td class="border-b border-r border-gray-100 px-3 py-1.5 text-gray-700" title={displayValue(row[header])}>
|
||||
<span class="block max-w-[200px] truncate">{displayValue(row[header])}</span>
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
70
src/lib/components/JsonPreview.svelte
Normal file
70
src/lib/components/JsonPreview.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
let { json }: { json: Record<string, unknown>[] } = $props();
|
||||
|
||||
let collapsed = $state(false);
|
||||
|
||||
const jsonString = $derived(JSON.stringify(json, null, 2));
|
||||
|
||||
/**
|
||||
* Simple JSON syntax highlighting — light theme.
|
||||
*/
|
||||
function highlight(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"([^"]*)"(?=\s*:)/g, '<span class="json-key">"$1"</span>')
|
||||
.replace(/:\s*"([^"]*)"/g, ': <span class="json-string">"$1"</span>')
|
||||
.replace(/:\s*(\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
|
||||
.replace(/:\s*(true|false)/g, ': <span class="json-bool">$1</span>')
|
||||
.replace(/:\s*(null)/g, ': <span class="json-null">$1</span>');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="flex flex-shrink-0 items-center justify-between border-b border-gray-200 bg-gray-50 px-4 py-2">
|
||||
<h3 class="text-sm font-semibold text-gray-700">
|
||||
JSON 预览
|
||||
<span class="ml-2 text-xs font-normal text-gray-400">{json.length} 条记录</span>
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={() => (collapsed = !collapsed)}
|
||||
class="rounded px-2 py-1 text-xs text-gray-500 hover:bg-gray-200 cursor-pointer"
|
||||
>
|
||||
{collapsed ? '展开' : '折叠'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="json-preview flex-1 overflow-auto p-4">
|
||||
{#if collapsed}
|
||||
<pre class="font-mono text-xs leading-relaxed text-gray-600">{JSON.stringify(json)}</pre>
|
||||
{:else}
|
||||
<pre class="font-mono text-xs leading-relaxed text-gray-500">{@html highlight(jsonString)}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.json-preview {
|
||||
background: #fafbfc;
|
||||
}
|
||||
:global(.json-key) {
|
||||
color: #24292e;
|
||||
font-weight: 500;
|
||||
}
|
||||
:global(.json-string) {
|
||||
color: #22863a;
|
||||
}
|
||||
:global(.json-number) {
|
||||
color: #005cc5;
|
||||
}
|
||||
:global(.json-bool) {
|
||||
color: #d73a49;
|
||||
}
|
||||
:global(.json-null) {
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
145
src/lib/converter.spec.ts
Normal file
145
src/lib/converter.spec.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { convertData, createDefaultMappings, applyTemplate, exportTemplate } from '$lib/converter.js';
|
||||
import type { MappingConfig, RowData } from '$lib/types.js';
|
||||
|
||||
describe('createDefaultMappings', () => {
|
||||
it('creates mappings from headers with correct defaults', () => {
|
||||
const mappings = createDefaultMappings(['姓名', '年龄']);
|
||||
expect(mappings).toHaveLength(2);
|
||||
expect(mappings[0]).toEqual({
|
||||
source: '姓名',
|
||||
target: '姓名',
|
||||
type: 'string',
|
||||
format: undefined,
|
||||
excludeIfEmpty: false,
|
||||
defaultValue: '',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-detects date columns from row data', () => {
|
||||
const rows: RowData[] = [
|
||||
{ name: 'Alice', birthday: new Date('2000-01-01') },
|
||||
{ name: 'Bob', birthday: new Date('1995-06-15') }
|
||||
];
|
||||
const mappings = createDefaultMappings(['name', 'birthday'], rows);
|
||||
expect(mappings[0].type).toBe('string');
|
||||
expect(mappings[1].type).toBe('date');
|
||||
expect(mappings[1].format).toBe('YYYY-MM-DD');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertData', () => {
|
||||
const rows: RowData[] = [
|
||||
{ name: 'Alice', age: 30, active: 'true', notes: '' },
|
||||
{ name: 'Bob', age: 25, active: 'false', notes: 'some note' }
|
||||
];
|
||||
|
||||
it('converts with default string mappings', () => {
|
||||
const mappings = createDefaultMappings(['name', 'age']);
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({ name: 'Alice', age: '30' });
|
||||
});
|
||||
|
||||
it('converts number type', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'age', target: 'age', type: 'number', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ age: 30 });
|
||||
});
|
||||
|
||||
it('converts boolean type', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'active', target: 'isActive', type: 'boolean', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ isActive: true });
|
||||
expect(result[1]).toEqual({ isActive: false });
|
||||
});
|
||||
|
||||
it('excludes empty fields when excludeIfEmpty is true', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'notes', target: 'notes', type: 'string', excludeIfEmpty: true, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({});
|
||||
expect(result[1]).toEqual({ notes: 'some note' });
|
||||
});
|
||||
|
||||
it('uses default value for empty fields', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'notes', target: 'notes', type: 'string', excludeIfEmpty: false, defaultValue: 'N/A', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ notes: 'N/A' });
|
||||
});
|
||||
|
||||
it('returns null for empty fields with no default', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'notes', target: 'notes', type: 'string', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ notes: null });
|
||||
});
|
||||
|
||||
it('skips disabled columns', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'name', target: 'name', type: 'string', excludeIfEmpty: false, defaultValue: '', enabled: false },
|
||||
{ source: 'age', target: 'age', type: 'number', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ age: 30 });
|
||||
});
|
||||
|
||||
it('renames target keys', () => {
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'name', target: 'full_name', type: 'string', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0]).toEqual({ full_name: 'Alice' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('date conversion', () => {
|
||||
it('formats Excel serial date numbers', () => {
|
||||
const rows: RowData[] = [{ date: 44927 }]; // 2023-01-01
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'date', target: 'date', type: 'date', format: 'YYYY-MM-DD', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(result[0].date).toBe('2023-01-01');
|
||||
});
|
||||
|
||||
it('returns unix timestamp when format is timestamp', () => {
|
||||
const rows: RowData[] = [{ date: '2023-01-01' }];
|
||||
const mappings: MappingConfig[] = [
|
||||
{ source: 'date', target: 'date', type: 'date', format: 'timestamp', excludeIfEmpty: false, defaultValue: '', enabled: true }
|
||||
];
|
||||
const result = convertData(rows, mappings);
|
||||
expect(typeof result[0].date).toBe('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template system', () => {
|
||||
it('exports and applies templates', () => {
|
||||
const mappings = createDefaultMappings(['姓名', '入职日期']);
|
||||
mappings[0].target = 'userName';
|
||||
mappings[1].type = 'date';
|
||||
mappings[1].format = 'YYYY-MM-DD';
|
||||
mappings[1].target = 'joinDate';
|
||||
|
||||
const template = exportTemplate(mappings);
|
||||
expect(template).toHaveLength(2);
|
||||
expect(template[0].target).toBe('userName');
|
||||
expect(template[1].format).toBe('YYYY-MM-DD');
|
||||
|
||||
// Apply to fresh mappings
|
||||
const freshMappings = createDefaultMappings(['姓名', '入职日期', '备注']);
|
||||
const applied = applyTemplate(freshMappings, template);
|
||||
expect(applied[0].target).toBe('userName');
|
||||
expect(applied[1].type).toBe('date');
|
||||
expect(applied[2].target).toBe('备注'); // not in template, unchanged
|
||||
});
|
||||
});
|
||||
172
src/lib/converter.ts
Normal file
172
src/lib/converter.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import dayjs from 'dayjs';
|
||||
import type { MappingConfig, RowData, DataType, MappingTemplate } from './types.js';
|
||||
|
||||
/**
|
||||
* Check if a value is considered empty.
|
||||
*/
|
||||
function isEmpty(value: unknown): boolean {
|
||||
return value === undefined || value === null || value === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a raw cell value to the specified data type.
|
||||
*/
|
||||
function convertValue(value: unknown, type: DataType, format?: string): unknown {
|
||||
if (isEmpty(value)) return undefined;
|
||||
|
||||
switch (type) {
|
||||
case 'number': {
|
||||
const num = Number(value);
|
||||
return isNaN(num) ? value : num;
|
||||
}
|
||||
case 'boolean': {
|
||||
if (typeof value === 'boolean') return value;
|
||||
const str = String(value).toLowerCase().trim();
|
||||
if (['true', '1', 'yes', '是'].includes(str)) return true;
|
||||
if (['false', '0', 'no', '否'].includes(str)) return false;
|
||||
return Boolean(value);
|
||||
}
|
||||
case 'date': {
|
||||
return formatDate(value, format);
|
||||
}
|
||||
case 'string':
|
||||
default:
|
||||
if (value instanceof Date) return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value as a date string. Handles Excel serial date numbers.
|
||||
*/
|
||||
function formatDate(value: unknown, format?: string): string | number {
|
||||
let date: dayjs.Dayjs;
|
||||
|
||||
if (value instanceof Date) {
|
||||
date = dayjs(value);
|
||||
} else if (typeof value === 'number') {
|
||||
// Excel serial date: days since 1900-01-01 (with the 1900 leap year bug)
|
||||
// Use UTC to avoid timezone issues
|
||||
const excelEpochMs = Date.UTC(1899, 11, 30);
|
||||
const ms = excelEpochMs + value * 86400000;
|
||||
date = dayjs(new Date(ms));
|
||||
} else {
|
||||
date = dayjs(value as string);
|
||||
}
|
||||
|
||||
if (!date.isValid()) return String(value);
|
||||
|
||||
if (format === 'timestamp') {
|
||||
return date.unix();
|
||||
}
|
||||
|
||||
return date.format(format || 'YYYY-MM-DD');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw Excel rows to JSON objects based on mapping configs.
|
||||
*/
|
||||
export function convertData(
|
||||
rows: RowData[],
|
||||
mappings: MappingConfig[]
|
||||
): Record<string, unknown>[] {
|
||||
const enabledMappings = mappings.filter((m) => m.enabled);
|
||||
|
||||
return rows.map((row) => {
|
||||
const obj: Record<string, unknown> = {};
|
||||
|
||||
for (const mapping of enabledMappings) {
|
||||
const rawValue = row[mapping.source];
|
||||
const isEmptyVal = isEmpty(rawValue);
|
||||
|
||||
if (isEmptyVal && mapping.excludeIfEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isEmptyVal) {
|
||||
obj[mapping.target] = mapping.defaultValue !== undefined && mapping.defaultValue !== ''
|
||||
? convertValue(mapping.defaultValue, mapping.type, mapping.format)
|
||||
: null;
|
||||
} else {
|
||||
obj[mapping.target] = convertValue(rawValue, mapping.type, mapping.format);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default mapping configs from Excel headers.
|
||||
* Auto-detects date columns by sampling row data.
|
||||
*/
|
||||
export function createDefaultMappings(headers: string[], rows?: RowData[]): MappingConfig[] {
|
||||
return headers.map((header) => {
|
||||
const detectedType = rows ? detectColumnType(header, rows) : 'string';
|
||||
return {
|
||||
source: header,
|
||||
target: header,
|
||||
type: detectedType,
|
||||
format: detectedType === 'date' ? 'YYYY-MM-DD' : undefined,
|
||||
excludeIfEmpty: false,
|
||||
defaultValue: '',
|
||||
enabled: true
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the data type of a column by sampling its values.
|
||||
*/
|
||||
function detectColumnType(header: string, rows: RowData[]): DataType {
|
||||
const sample = rows.slice(0, 20);
|
||||
let dateCount = 0;
|
||||
let totalNonEmpty = 0;
|
||||
|
||||
for (const row of sample) {
|
||||
const val = row[header];
|
||||
if (val === undefined || val === null || val === '') continue;
|
||||
totalNonEmpty++;
|
||||
if (val instanceof Date) dateCount++;
|
||||
}
|
||||
|
||||
if (totalNonEmpty > 0 && dateCount / totalNonEmpty >= 0.5) return 'date';
|
||||
return 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a template to existing mappings. Matches by source header name.
|
||||
*/
|
||||
export function applyTemplate(
|
||||
currentMappings: MappingConfig[],
|
||||
template: MappingTemplate
|
||||
): MappingConfig[] {
|
||||
const templateMap = new Map(template.map((t) => [t.source, t]));
|
||||
|
||||
return currentMappings.map((mapping) => {
|
||||
const tmpl = templateMap.get(mapping.source);
|
||||
if (tmpl) {
|
||||
return {
|
||||
...mapping,
|
||||
target: tmpl.target,
|
||||
type: tmpl.type,
|
||||
format: tmpl.format,
|
||||
excludeIfEmpty: tmpl.excludeIfEmpty,
|
||||
defaultValue: tmpl.defaultValue ?? ''
|
||||
};
|
||||
}
|
||||
return mapping;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export current mappings as a template.
|
||||
*/
|
||||
export function exportTemplate(mappings: MappingConfig[]): MappingTemplate {
|
||||
return mappings.map(({ source, target, type, format, excludeIfEmpty, defaultValue }) => {
|
||||
const entry: MappingTemplate[number] = { source, target, type, excludeIfEmpty };
|
||||
if (type === 'date' && format) entry.format = format;
|
||||
if (defaultValue) entry.defaultValue = defaultValue;
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
44
src/lib/excel.ts
Normal file
44
src/lib/excel.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
import type { RowData } from './types.js';
|
||||
|
||||
export interface ParsedExcel {
|
||||
headers: string[];
|
||||
rows: RowData[];
|
||||
sheetNames: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an Excel/CSV file into headers and rows.
|
||||
*/
|
||||
export async function parseExcelFile(file: File): Promise<ParsedExcel> {
|
||||
const buffer = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(buffer, { type: 'array', cellDates: true });
|
||||
const sheetNames = workbook.SheetNames;
|
||||
|
||||
if (sheetNames.length === 0) {
|
||||
throw new Error('文件中没有找到任何工作表');
|
||||
}
|
||||
|
||||
return parseSheet(workbook, sheetNames[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a specific sheet from a workbook.
|
||||
*/
|
||||
export function parseSheet(workbook: XLSX.WorkBook, sheetName: string): ParsedExcel {
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
if (!sheet) {
|
||||
throw new Error(`工作表 "${sheetName}" 不存在`);
|
||||
}
|
||||
|
||||
const jsonData = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, {
|
||||
defval: ''
|
||||
});
|
||||
|
||||
if (jsonData.length === 0) {
|
||||
throw new Error('工作表中没有数据');
|
||||
}
|
||||
|
||||
const headers = Object.keys(jsonData[0]);
|
||||
return { headers, rows: jsonData, sheetNames: workbook.SheetNames };
|
||||
}
|
||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
35
src/lib/types.ts
Normal file
35
src/lib/types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type DataType = 'string' | 'number' | 'boolean' | 'date';
|
||||
|
||||
export type DateFormat =
|
||||
| 'YYYY-MM-DD'
|
||||
| 'YYYY/MM/DD'
|
||||
| 'YYYY-MM-DD HH:mm'
|
||||
| 'YYYY/MM/DD HH:mm'
|
||||
| 'YYYY-MM-DD HH:mm:ss'
|
||||
| 'timestamp';
|
||||
|
||||
export interface MappingConfig {
|
||||
/** Excel original header name (read-only) */
|
||||
source: string;
|
||||
/** Target key name in JSON output */
|
||||
target: string;
|
||||
/** Data type for conversion */
|
||||
type: DataType;
|
||||
/** Date format string (only used when type is 'date') */
|
||||
format?: DateFormat;
|
||||
/** If true, exclude this key from JSON when cell is empty */
|
||||
excludeIfEmpty: boolean;
|
||||
/** Default value when cell is empty and excludeIfEmpty is false */
|
||||
defaultValue?: string;
|
||||
/** Whether this column is included in output */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/** A single row of raw Excel data, keyed by original header */
|
||||
export type RowData = Record<string, unknown>;
|
||||
|
||||
/** Template file structure for import/export */
|
||||
export type MappingTemplate = Pick<
|
||||
MappingConfig,
|
||||
'source' | 'target' | 'type' | 'format' | 'excludeIfEmpty' | 'defaultValue'
|
||||
>[];
|
||||
9
src/routes/+layout.svelte
Normal file
9
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import './layout.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
||||
{@render children()}
|
||||
259
src/routes/+page.svelte
Normal file
259
src/routes/+page.svelte
Normal file
@@ -0,0 +1,259 @@
|
||||
<script lang="ts">
|
||||
import type { MappingConfig, RowData, MappingTemplate } from '$lib/types.js';
|
||||
import { parseExcelFile } from '$lib/excel.js';
|
||||
import { convertData, createDefaultMappings, applyTemplate, exportTemplate } from '$lib/converter.js';
|
||||
import ExcelTable from '$lib/components/ExcelTable.svelte';
|
||||
import JsonPreview from '$lib/components/JsonPreview.svelte';
|
||||
|
||||
// State
|
||||
let headers = $state<string[]>([]);
|
||||
let rows = $state<RowData[]>([]);
|
||||
let mappings = $state<MappingConfig[]>([]);
|
||||
let errorMessage = $state('');
|
||||
let fileName = $state('');
|
||||
let isDragOver = $state(false);
|
||||
let copySuccess = $state(false);
|
||||
|
||||
// Derived
|
||||
const hasData = $derived(headers.length > 0 && rows.length > 0);
|
||||
const convertedJson = $derived(hasData ? convertData(rows, mappings) : []);
|
||||
const jsonString = $derived(JSON.stringify(convertedJson, null, 2));
|
||||
|
||||
// File handling
|
||||
async function handleFile(file: File) {
|
||||
const validExtensions = ['.xlsx', '.xls', '.csv'];
|
||||
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
|
||||
if (!validExtensions.includes(ext)) {
|
||||
errorMessage = `不支持的文件格式: ${ext}。请上传 .xlsx, .xls 或 .csv 文件。`;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
errorMessage = '';
|
||||
const result = await parseExcelFile(file);
|
||||
headers = result.headers;
|
||||
rows = result.rows;
|
||||
mappings = createDefaultMappings(result.headers, result.rows);
|
||||
fileName = file.name;
|
||||
} catch (e) {
|
||||
errorMessage = e instanceof Error ? e.message : '文件解析失败';
|
||||
headers = [];
|
||||
rows = [];
|
||||
mappings = [];
|
||||
}
|
||||
}
|
||||
|
||||
function onFileInput(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
if (file) handleFile(file);
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
isDragOver = false;
|
||||
const file = e.dataTransfer?.files[0];
|
||||
if (file) handleFile(file);
|
||||
}
|
||||
|
||||
function onDragOver(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
isDragOver = true;
|
||||
}
|
||||
|
||||
function onDragLeave() {
|
||||
isDragOver = false;
|
||||
}
|
||||
|
||||
// Template handling
|
||||
function handleExportTemplate() {
|
||||
const template = exportTemplate(mappings);
|
||||
const blob = new Blob([JSON.stringify(template, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName ? fileName.replace(/\.[^.]+$/, '_mapping.json') : 'mapping_template.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function handleImportTemplate() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = async () => {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
const text = await file.text();
|
||||
const template: MappingTemplate = JSON.parse(text);
|
||||
if (!Array.isArray(template)) {
|
||||
errorMessage = '无效的模板格式';
|
||||
return;
|
||||
}
|
||||
mappings = applyTemplate(mappings, template);
|
||||
errorMessage = '';
|
||||
} catch {
|
||||
errorMessage = '模板文件解析失败';
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
// JSON operations
|
||||
function handleDownloadJson() {
|
||||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName ? fileName.replace(/\.[^.]+$/, '.json') : 'output.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async function handleCopyJson() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(jsonString);
|
||||
copySuccess = true;
|
||||
setTimeout(() => (copySuccess = false), 2000);
|
||||
} catch {
|
||||
errorMessage = '复制失败,请手动复制';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex h-screen flex-col bg-gray-100"
|
||||
ondrop={onDrop}
|
||||
ondragover={onDragOver}
|
||||
ondragleave={onDragLeave}
|
||||
role="application"
|
||||
>
|
||||
<!-- Header Toolbar -->
|
||||
<header class="flex flex-shrink-0 items-center gap-3 border-b border-gray-200 bg-white px-4 py-3 shadow-sm">
|
||||
<h1 class="text-lg font-bold text-gray-800">Excel → JSON</h1>
|
||||
|
||||
<div class="mx-2 h-6 w-px bg-gray-200"></div>
|
||||
|
||||
<!-- File upload -->
|
||||
<label class="inline-flex cursor-pointer items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
上传文件
|
||||
<input type="file" accept=".xlsx,.xls,.csv" onchange={onFileInput} class="hidden" />
|
||||
</label>
|
||||
|
||||
{#if fileName}
|
||||
<span class="text-sm text-gray-500">{fileName}</span>
|
||||
{/if}
|
||||
|
||||
<div class="mx-2 h-6 w-px bg-gray-200"></div>
|
||||
|
||||
<!-- Template operations -->
|
||||
<button
|
||||
onclick={handleImportTemplate}
|
||||
disabled={!hasData}
|
||||
class="inline-flex items-center gap-1 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||
</svg>
|
||||
导入配置
|
||||
</button>
|
||||
<button
|
||||
onclick={handleExportTemplate}
|
||||
disabled={!hasData}
|
||||
class="inline-flex items-center gap-1 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
导出配置
|
||||
</button>
|
||||
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- JSON operations -->
|
||||
<button
|
||||
onclick={handleDownloadJson}
|
||||
disabled={!hasData}
|
||||
class="inline-flex items-center gap-1 rounded-md bg-green-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-green-700 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
下载 JSON
|
||||
</button>
|
||||
<button
|
||||
onclick={handleCopyJson}
|
||||
disabled={!hasData}
|
||||
class="inline-flex items-center gap-1 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{#if copySuccess}
|
||||
<svg class="h-4 w-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
已复制
|
||||
{:else}
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
复制 JSON
|
||||
{/if}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Error message -->
|
||||
{#if errorMessage}
|
||||
<div class="flex items-center gap-2 bg-red-50 px-4 py-2 text-sm text-red-700">
|
||||
<svg class="h-4 w-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{errorMessage}
|
||||
<button onclick={() => (errorMessage = '')} aria-label="关闭错误" class="ml-auto text-red-500 hover:text-red-700 cursor-pointer">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Main content -->
|
||||
{#if hasData}
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Left: Excel Table -->
|
||||
<div class="w-1/2 overflow-hidden border-r border-gray-300">
|
||||
<ExcelTable {headers} {rows} bind:mappings />
|
||||
</div>
|
||||
<!-- Right: JSON Preview -->
|
||||
<div class="w-1/2 overflow-hidden">
|
||||
<JsonPreview json={convertedJson} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Empty state / drop zone -->
|
||||
<div class="flex flex-1 items-center justify-center p-8">
|
||||
<div
|
||||
class="flex max-w-md flex-col items-center rounded-2xl border-2 border-dashed p-12 text-center transition-colors {isDragOver
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-300 bg-white'}"
|
||||
>
|
||||
<svg class="mb-4 h-16 w-16 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<h2 class="mb-2 text-xl font-semibold text-gray-700">拖拽 Excel 文件到此处</h2>
|
||||
<p class="mb-4 text-sm text-gray-500">支持 .xlsx, .xls, .csv 格式</p>
|
||||
<label class="inline-flex cursor-pointer items-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white hover:bg-blue-700">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
选择文件
|
||||
<input type="file" accept=".xlsx,.xls,.csv" onchange={onFileInput} class="hidden" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
3
src/routes/layout.css
Normal file
3
src/routes/layout.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/forms';
|
||||
@plugin '@tailwindcss/typography';
|
||||
21
src/routes/page.svelte.spec.ts
Normal file
21
src/routes/page.svelte.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { page } from 'vitest/browser';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import Page from './+page.svelte';
|
||||
|
||||
describe('/+page.svelte', () => {
|
||||
it('should render the app title', async () => {
|
||||
render(Page);
|
||||
|
||||
const heading = page.getByRole('heading', { level: 1 });
|
||||
await expect.element(heading).toBeInTheDocument();
|
||||
await expect.element(heading).toHaveTextContent('Excel → JSON');
|
||||
});
|
||||
|
||||
it('should show the drop zone when no file is loaded', async () => {
|
||||
render(Page);
|
||||
|
||||
const dropText = page.getByText('拖拽 Excel 文件到此处');
|
||||
await expect.element(dropText).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
13
svelte.config.js
Normal file
13
svelte.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
37
vite.config.ts
Normal file
37
vite.config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import devtoolsJson from 'vite-plugin-devtools-json';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit(), devtoolsJson()],
|
||||
test: {
|
||||
expect: { requireAssertions: true },
|
||||
projects: [
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'client',
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium', headless: true }]
|
||||
},
|
||||
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/lib/server/**']
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
extends: './vite.config.ts',
|
||||
test: {
|
||||
name: 'server',
|
||||
environment: 'node',
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
859
yarn.lock
Normal file
859
yarn.lock
Normal file
@@ -0,0 +1,859 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@esbuild/win32-x64@0.27.3":
|
||||
version "0.27.3"
|
||||
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz"
|
||||
integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz"
|
||||
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/remapping@^2.3.4":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz"
|
||||
integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
|
||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
|
||||
version "0.3.31"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz"
|
||||
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@polka/url@^1.0.0-next.24":
|
||||
version "1.0.0-next.29"
|
||||
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz"
|
||||
integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu@4.57.1":
|
||||
version "4.57.1"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz"
|
||||
integrity sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.57.1":
|
||||
version "4.57.1"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz"
|
||||
integrity sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==
|
||||
|
||||
"@standard-schema/spec@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz"
|
||||
integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==
|
||||
|
||||
"@sveltejs/acorn-typescript@^1.0.5":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz"
|
||||
integrity sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==
|
||||
|
||||
"@sveltejs/adapter-auto@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz"
|
||||
integrity sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==
|
||||
|
||||
"@sveltejs/kit@^2.0.0", "@sveltejs/kit@^2.50.2":
|
||||
version "2.50.2"
|
||||
resolved "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz"
|
||||
integrity sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==
|
||||
dependencies:
|
||||
"@standard-schema/spec" "^1.0.0"
|
||||
"@sveltejs/acorn-typescript" "^1.0.5"
|
||||
"@types/cookie" "^0.6.0"
|
||||
acorn "^8.14.1"
|
||||
cookie "^0.6.0"
|
||||
devalue "^5.6.2"
|
||||
esm-env "^1.2.2"
|
||||
kleur "^4.1.5"
|
||||
magic-string "^0.30.5"
|
||||
mrmime "^2.0.0"
|
||||
sade "^1.8.1"
|
||||
set-cookie-parser "^3.0.0"
|
||||
sirv "^3.0.0"
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz"
|
||||
integrity sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==
|
||||
dependencies:
|
||||
obug "^2.1.0"
|
||||
|
||||
"@sveltejs/vite-plugin-svelte@^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "@sveltejs/vite-plugin-svelte@^6.0.0-next.0", "@sveltejs/vite-plugin-svelte@^6.2.4":
|
||||
version "6.2.4"
|
||||
resolved "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz"
|
||||
integrity sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==
|
||||
dependencies:
|
||||
"@sveltejs/vite-plugin-svelte-inspector" "^5.0.0"
|
||||
deepmerge "^4.3.1"
|
||||
magic-string "^0.30.21"
|
||||
obug "^2.1.0"
|
||||
vitefu "^1.1.1"
|
||||
|
||||
"@tailwindcss/forms@^0.5.11":
|
||||
version "0.5.11"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz"
|
||||
integrity sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@tailwindcss/node@4.1.18":
|
||||
version "4.1.18"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz"
|
||||
integrity sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==
|
||||
dependencies:
|
||||
"@jridgewell/remapping" "^2.3.4"
|
||||
enhanced-resolve "^5.18.3"
|
||||
jiti "^2.6.1"
|
||||
lightningcss "1.30.2"
|
||||
magic-string "^0.30.21"
|
||||
source-map-js "^1.2.1"
|
||||
tailwindcss "4.1.18"
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.18":
|
||||
version "4.1.18"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz"
|
||||
integrity sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==
|
||||
|
||||
"@tailwindcss/oxide@4.1.18":
|
||||
version "4.1.18"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz"
|
||||
integrity sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==
|
||||
optionalDependencies:
|
||||
"@tailwindcss/oxide-android-arm64" "4.1.18"
|
||||
"@tailwindcss/oxide-darwin-arm64" "4.1.18"
|
||||
"@tailwindcss/oxide-darwin-x64" "4.1.18"
|
||||
"@tailwindcss/oxide-freebsd-x64" "4.1.18"
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.18"
|
||||
"@tailwindcss/oxide-linux-arm64-gnu" "4.1.18"
|
||||
"@tailwindcss/oxide-linux-arm64-musl" "4.1.18"
|
||||
"@tailwindcss/oxide-linux-x64-gnu" "4.1.18"
|
||||
"@tailwindcss/oxide-linux-x64-musl" "4.1.18"
|
||||
"@tailwindcss/oxide-wasm32-wasi" "4.1.18"
|
||||
"@tailwindcss/oxide-win32-arm64-msvc" "4.1.18"
|
||||
"@tailwindcss/oxide-win32-x64-msvc" "4.1.18"
|
||||
|
||||
"@tailwindcss/typography@^0.5.19":
|
||||
version "0.5.19"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz"
|
||||
integrity sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==
|
||||
dependencies:
|
||||
postcss-selector-parser "6.0.10"
|
||||
|
||||
"@tailwindcss/vite@^4.1.18":
|
||||
version "4.1.18"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz"
|
||||
integrity sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==
|
||||
dependencies:
|
||||
"@tailwindcss/node" "4.1.18"
|
||||
"@tailwindcss/oxide" "4.1.18"
|
||||
tailwindcss "4.1.18"
|
||||
|
||||
"@testing-library/svelte-core@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.0.0.tgz"
|
||||
integrity sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==
|
||||
|
||||
"@types/chai@^5.2.2":
|
||||
version "5.2.3"
|
||||
resolved "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz"
|
||||
integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==
|
||||
dependencies:
|
||||
"@types/deep-eql" "*"
|
||||
assertion-error "^2.0.1"
|
||||
|
||||
"@types/cookie@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz"
|
||||
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
|
||||
|
||||
"@types/deep-eql@*":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz"
|
||||
integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==
|
||||
|
||||
"@types/estree@^1.0.0", "@types/estree@^1.0.5", "@types/estree@^1.0.6", "@types/estree@1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@vitest/browser-playwright@^4.0.18", "@vitest/browser-playwright@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.18.tgz"
|
||||
integrity sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==
|
||||
dependencies:
|
||||
"@vitest/browser" "4.0.18"
|
||||
"@vitest/mocker" "4.0.18"
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
"@vitest/browser@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.18.tgz"
|
||||
integrity sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==
|
||||
dependencies:
|
||||
"@vitest/mocker" "4.0.18"
|
||||
"@vitest/utils" "4.0.18"
|
||||
magic-string "^0.30.21"
|
||||
pixelmatch "7.1.0"
|
||||
pngjs "^7.0.0"
|
||||
sirv "^3.0.2"
|
||||
tinyrainbow "^3.0.3"
|
||||
ws "^8.18.3"
|
||||
|
||||
"@vitest/expect@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz"
|
||||
integrity sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==
|
||||
dependencies:
|
||||
"@standard-schema/spec" "^1.0.0"
|
||||
"@types/chai" "^5.2.2"
|
||||
"@vitest/spy" "4.0.18"
|
||||
"@vitest/utils" "4.0.18"
|
||||
chai "^6.2.1"
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
"@vitest/mocker@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz"
|
||||
integrity sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==
|
||||
dependencies:
|
||||
"@vitest/spy" "4.0.18"
|
||||
estree-walker "^3.0.3"
|
||||
magic-string "^0.30.21"
|
||||
|
||||
"@vitest/pretty-format@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz"
|
||||
integrity sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==
|
||||
dependencies:
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
"@vitest/runner@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz"
|
||||
integrity sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==
|
||||
dependencies:
|
||||
"@vitest/utils" "4.0.18"
|
||||
pathe "^2.0.3"
|
||||
|
||||
"@vitest/snapshot@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz"
|
||||
integrity sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "4.0.18"
|
||||
magic-string "^0.30.21"
|
||||
pathe "^2.0.3"
|
||||
|
||||
"@vitest/spy@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz"
|
||||
integrity sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==
|
||||
|
||||
"@vitest/utils@4.0.18":
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz"
|
||||
integrity sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "4.0.18"
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
acorn@^8.12.1, acorn@^8.14.1, acorn@^8.9.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
|
||||
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
||||
|
||||
adler-32@~1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz"
|
||||
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
|
||||
|
||||
aria-query@^5.3.1:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz"
|
||||
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
|
||||
|
||||
assertion-error@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz"
|
||||
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
|
||||
|
||||
axobject-query@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
|
||||
integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
|
||||
|
||||
cfb@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz"
|
||||
integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
|
||||
dependencies:
|
||||
adler-32 "~1.3.0"
|
||||
crc-32 "~1.2.0"
|
||||
|
||||
chai@^6.2.1:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz"
|
||||
integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==
|
||||
|
||||
chokidar@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz"
|
||||
integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
|
||||
dependencies:
|
||||
readdirp "^4.0.1"
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
codepage@~1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz"
|
||||
integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
|
||||
|
||||
cookie@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
|
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||
|
||||
crc-32@~1.2.0, crc-32@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
dayjs@^1.11.19:
|
||||
version "1.11.19"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz"
|
||||
integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
|
||||
|
||||
deepmerge@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
|
||||
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
|
||||
|
||||
detect-libc@^2.0.3:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
|
||||
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
|
||||
|
||||
devalue@^5.6.2:
|
||||
version "5.6.2"
|
||||
resolved "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz"
|
||||
integrity sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==
|
||||
|
||||
enhanced-resolve@^5.18.3:
|
||||
version "5.19.0"
|
||||
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz"
|
||||
integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
tapable "^2.3.0"
|
||||
|
||||
es-module-lexer@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
|
||||
integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
|
||||
|
||||
esbuild@^0.27.0:
|
||||
version "0.27.3"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz"
|
||||
integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==
|
||||
optionalDependencies:
|
||||
"@esbuild/aix-ppc64" "0.27.3"
|
||||
"@esbuild/android-arm" "0.27.3"
|
||||
"@esbuild/android-arm64" "0.27.3"
|
||||
"@esbuild/android-x64" "0.27.3"
|
||||
"@esbuild/darwin-arm64" "0.27.3"
|
||||
"@esbuild/darwin-x64" "0.27.3"
|
||||
"@esbuild/freebsd-arm64" "0.27.3"
|
||||
"@esbuild/freebsd-x64" "0.27.3"
|
||||
"@esbuild/linux-arm" "0.27.3"
|
||||
"@esbuild/linux-arm64" "0.27.3"
|
||||
"@esbuild/linux-ia32" "0.27.3"
|
||||
"@esbuild/linux-loong64" "0.27.3"
|
||||
"@esbuild/linux-mips64el" "0.27.3"
|
||||
"@esbuild/linux-ppc64" "0.27.3"
|
||||
"@esbuild/linux-riscv64" "0.27.3"
|
||||
"@esbuild/linux-s390x" "0.27.3"
|
||||
"@esbuild/linux-x64" "0.27.3"
|
||||
"@esbuild/netbsd-arm64" "0.27.3"
|
||||
"@esbuild/netbsd-x64" "0.27.3"
|
||||
"@esbuild/openbsd-arm64" "0.27.3"
|
||||
"@esbuild/openbsd-x64" "0.27.3"
|
||||
"@esbuild/openharmony-arm64" "0.27.3"
|
||||
"@esbuild/sunos-x64" "0.27.3"
|
||||
"@esbuild/win32-arm64" "0.27.3"
|
||||
"@esbuild/win32-ia32" "0.27.3"
|
||||
"@esbuild/win32-x64" "0.27.3"
|
||||
|
||||
esm-env@^1.2.1, esm-env@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz"
|
||||
integrity sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==
|
||||
|
||||
esrap@^2.2.2:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz"
|
||||
integrity sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
|
||||
estree-walker@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz"
|
||||
integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
|
||||
expect-type@^1.2.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz"
|
||||
integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==
|
||||
|
||||
fdir@^6.2.0, fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
frac@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz"
|
||||
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
|
||||
|
||||
graceful-fs@^4.2.4:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
is-reference@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz"
|
||||
integrity sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.6"
|
||||
|
||||
jiti@^2.6.1, jiti@>=1.21.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz"
|
||||
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
|
||||
|
||||
kleur@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz"
|
||||
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz"
|
||||
integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==
|
||||
|
||||
lightningcss@^1.21.0, lightningcss@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz"
|
||||
integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==
|
||||
dependencies:
|
||||
detect-libc "^2.0.3"
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64 "1.30.2"
|
||||
lightningcss-darwin-arm64 "1.30.2"
|
||||
lightningcss-darwin-x64 "1.30.2"
|
||||
lightningcss-freebsd-x64 "1.30.2"
|
||||
lightningcss-linux-arm-gnueabihf "1.30.2"
|
||||
lightningcss-linux-arm64-gnu "1.30.2"
|
||||
lightningcss-linux-arm64-musl "1.30.2"
|
||||
lightningcss-linux-x64-gnu "1.30.2"
|
||||
lightningcss-linux-x64-musl "1.30.2"
|
||||
lightningcss-win32-arm64-msvc "1.30.2"
|
||||
lightningcss-win32-x64-msvc "1.30.2"
|
||||
|
||||
locate-character@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz"
|
||||
integrity sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==
|
||||
|
||||
magic-string@^0.30.11, magic-string@^0.30.21, magic-string@^0.30.5:
|
||||
version "0.30.21"
|
||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz"
|
||||
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.5"
|
||||
|
||||
mini-svg-data-uri@^1.2.3:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz"
|
||||
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz"
|
||||
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
|
||||
|
||||
mrmime@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz"
|
||||
integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
|
||||
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.11"
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
obug@^2.1.0, obug@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz"
|
||||
integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==
|
||||
|
||||
pathe@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz"
|
||||
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
|
||||
|
||||
picocolors@^1.0.0, picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
"picomatch@^3 || ^4", picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
pixelmatch@7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz"
|
||||
integrity sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==
|
||||
dependencies:
|
||||
pngjs "^7.0.0"
|
||||
|
||||
playwright-core@1.58.2:
|
||||
version "1.58.2"
|
||||
resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz"
|
||||
integrity sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==
|
||||
|
||||
playwright@*, playwright@^1.58.1:
|
||||
version "1.58.2"
|
||||
resolved "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz"
|
||||
integrity sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==
|
||||
dependencies:
|
||||
playwright-core "1.58.2"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
pngjs@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz"
|
||||
integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==
|
||||
|
||||
postcss-selector-parser@6.0.10:
|
||||
version "6.0.10"
|
||||
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz"
|
||||
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss@^8.5.6:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
dependencies:
|
||||
nanoid "^3.3.11"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
readdirp@^4.0.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
|
||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.57.1"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz"
|
||||
integrity sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.8"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.57.1"
|
||||
"@rollup/rollup-android-arm64" "4.57.1"
|
||||
"@rollup/rollup-darwin-arm64" "4.57.1"
|
||||
"@rollup/rollup-darwin-x64" "4.57.1"
|
||||
"@rollup/rollup-freebsd-arm64" "4.57.1"
|
||||
"@rollup/rollup-freebsd-x64" "4.57.1"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.57.1"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.57.1"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.57.1"
|
||||
"@rollup/rollup-linux-loong64-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-loong64-musl" "4.57.1"
|
||||
"@rollup/rollup-linux-ppc64-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-ppc64-musl" "4.57.1"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-riscv64-musl" "4.57.1"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.57.1"
|
||||
"@rollup/rollup-linux-x64-musl" "4.57.1"
|
||||
"@rollup/rollup-openbsd-x64" "4.57.1"
|
||||
"@rollup/rollup-openharmony-arm64" "4.57.1"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.57.1"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.57.1"
|
||||
"@rollup/rollup-win32-x64-gnu" "4.57.1"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.57.1"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
sade@^1.7.4, sade@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz"
|
||||
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
|
||||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
set-cookie-parser@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz"
|
||||
integrity sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==
|
||||
|
||||
siginfo@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz"
|
||||
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
|
||||
|
||||
sirv@^3.0.0, sirv@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz"
|
||||
integrity sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==
|
||||
dependencies:
|
||||
"@polka/url" "^1.0.0-next.24"
|
||||
mrmime "^2.0.0"
|
||||
totalist "^3.0.0"
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
ssf@~0.11.2:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz"
|
||||
integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
|
||||
dependencies:
|
||||
frac "~1.1.2"
|
||||
|
||||
stackback@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz"
|
||||
integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
|
||||
|
||||
std-env@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz"
|
||||
integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
|
||||
|
||||
svelte-check@^4.3.6:
|
||||
version "4.3.6"
|
||||
resolved "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz"
|
||||
integrity sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
chokidar "^4.0.1"
|
||||
fdir "^6.2.0"
|
||||
picocolors "^1.0.0"
|
||||
sade "^1.7.4"
|
||||
|
||||
"svelte@^3 || ^4 || ^5 || ^5.0.0-next.0", "svelte@^4.0.0 || ^5.0.0-next.0", svelte@^5.0.0, svelte@^5.49.2:
|
||||
version "5.50.0"
|
||||
resolved "https://registry.npmjs.org/svelte/-/svelte-5.50.0.tgz"
|
||||
integrity sha512-FR9kTLmX5i0oyeQ5j/+w8DuagIkQ7MWMuPpPVioW2zx9Dw77q+1ufLzF1IqNtcTXPRnIIio4PlasliVn43OnbQ==
|
||||
dependencies:
|
||||
"@jridgewell/remapping" "^2.3.4"
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
"@sveltejs/acorn-typescript" "^1.0.5"
|
||||
"@types/estree" "^1.0.5"
|
||||
acorn "^8.12.1"
|
||||
aria-query "^5.3.1"
|
||||
axobject-query "^4.1.0"
|
||||
clsx "^2.1.1"
|
||||
devalue "^5.6.2"
|
||||
esm-env "^1.2.1"
|
||||
esrap "^2.2.2"
|
||||
is-reference "^3.0.3"
|
||||
locate-character "^3.0.0"
|
||||
magic-string "^0.30.11"
|
||||
zimmerframe "^1.1.2"
|
||||
|
||||
tailwindcss@^4.1.18, "tailwindcss@>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1", "tailwindcss@>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1", tailwindcss@4.1.18:
|
||||
version "4.1.18"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz"
|
||||
integrity sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==
|
||||
|
||||
tapable@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz"
|
||||
integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==
|
||||
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz"
|
||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||
|
||||
tinyexec@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz"
|
||||
integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==
|
||||
|
||||
tinyglobby@^0.2.15:
|
||||
version "0.2.15"
|
||||
resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz"
|
||||
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
|
||||
dependencies:
|
||||
fdir "^6.5.0"
|
||||
picomatch "^4.0.3"
|
||||
|
||||
tinyrainbow@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz"
|
||||
integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==
|
||||
|
||||
totalist@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz"
|
||||
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
|
||||
|
||||
typescript@^5.3.3, typescript@^5.9.3, typescript@>=5.0.0:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
uuid@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz"
|
||||
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
|
||||
|
||||
vite-plugin-devtools-json@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/vite-plugin-devtools-json/-/vite-plugin-devtools-json-1.0.0.tgz"
|
||||
integrity sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==
|
||||
dependencies:
|
||||
uuid "^11.1.0"
|
||||
|
||||
"vite@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0", "vite@^5.0.0 || ^6.0.0 || ^7.0.0", "vite@^5.0.3 || ^6.0.0 || ^7.0.0-beta.0", "vite@^5.2.0 || ^6 || ^7", "vite@^6.0.0 || ^7.0.0", "vite@^6.0.0 || ^7.0.0-0", "vite@^6.3.0 || ^7.0.0", vite@^7.3.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz"
|
||||
integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==
|
||||
dependencies:
|
||||
esbuild "^0.27.0"
|
||||
fdir "^6.5.0"
|
||||
picomatch "^4.0.3"
|
||||
postcss "^8.5.6"
|
||||
rollup "^4.43.0"
|
||||
tinyglobby "^0.2.15"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
vitefu@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz"
|
||||
integrity sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==
|
||||
|
||||
vitest-browser-svelte@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/vitest-browser-svelte/-/vitest-browser-svelte-2.0.2.tgz"
|
||||
integrity sha512-OLJVYoIYflwToFIy3s41pZ9mVp6dwXfYd8IIsWoc57g8DyN3SxsNJ5GB1xWFPxLFlKM+1MPExjPxLaqdELrfRQ==
|
||||
dependencies:
|
||||
"@testing-library/svelte-core" "^1.0.0"
|
||||
|
||||
vitest@^4.0.0, vitest@^4.0.18, vitest@4.0.18:
|
||||
version "4.0.18"
|
||||
resolved "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz"
|
||||
integrity sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==
|
||||
dependencies:
|
||||
"@vitest/expect" "4.0.18"
|
||||
"@vitest/mocker" "4.0.18"
|
||||
"@vitest/pretty-format" "4.0.18"
|
||||
"@vitest/runner" "4.0.18"
|
||||
"@vitest/snapshot" "4.0.18"
|
||||
"@vitest/spy" "4.0.18"
|
||||
"@vitest/utils" "4.0.18"
|
||||
es-module-lexer "^1.7.0"
|
||||
expect-type "^1.2.2"
|
||||
magic-string "^0.30.21"
|
||||
obug "^2.1.1"
|
||||
pathe "^2.0.3"
|
||||
picomatch "^4.0.3"
|
||||
std-env "^3.10.0"
|
||||
tinybench "^2.9.0"
|
||||
tinyexec "^1.0.2"
|
||||
tinyglobby "^0.2.15"
|
||||
tinyrainbow "^3.0.3"
|
||||
vite "^6.0.0 || ^7.0.0"
|
||||
why-is-node-running "^2.3.0"
|
||||
|
||||
why-is-node-running@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz"
|
||||
integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
|
||||
dependencies:
|
||||
siginfo "^2.0.0"
|
||||
stackback "0.0.2"
|
||||
|
||||
wmf@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz"
|
||||
integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
|
||||
|
||||
word@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz"
|
||||
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
|
||||
|
||||
ws@^8.18.3:
|
||||
version "8.19.0"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz"
|
||||
integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==
|
||||
|
||||
xlsx@^0.18.5:
|
||||
version "0.18.5"
|
||||
resolved "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz"
|
||||
integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
|
||||
dependencies:
|
||||
adler-32 "~1.3.0"
|
||||
cfb "~1.2.1"
|
||||
codepage "~1.15.0"
|
||||
crc-32 "~1.2.1"
|
||||
ssf "~0.11.2"
|
||||
wmf "~1.0.1"
|
||||
word "~0.3.0"
|
||||
|
||||
zimmerframe@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz"
|
||||
integrity sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==
|
||||
Reference in New Issue
Block a user