modscape 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -21
- package/package.json +2 -1
- package/src/export.js +193 -0
- package/src/index.js +11 -1
- package/visualizer/README.md +73 -0
- package/visualizer/package.json +44 -0
- package/visualizer/public/favicon.svg +24 -0
- package/visualizer-dist/assets/index-C60HDQhH.js +40 -0
- package/visualizer-dist/index.html +1 -1
- package/visualizer-dist/assets/index-CLmFaRYF.js +0 -40
package/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
# <img src="./visualizer
|
|
1
|
+
# <img src="./visualizer/public/favicon.svg" width="32" height="32" align="center" /> Modscape
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/modscape)
|
|
4
|
+
[](https://www.npmjs.com/package/modscape)
|
|
5
|
+
[](https://github.com/yujikawa/modscape/actions/workflows/deploy.yml)
|
|
6
|
+
[](https://github.com/yujikawa/modscape/actions/workflows/publish.yml)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
8
|
|
|
3
9
|
Modscape is a YAML-driven data modeling visualizer. It helps data engineers and architects bridge the gap between conceptual, logical, and physical data models while maintaining sample data "stories".
|
|
4
10
|
|
|
@@ -11,6 +17,7 @@ Modscape is a YAML-driven data modeling visualizer. It helps data engineers and
|
|
|
11
17
|
- **Sample Data "Stories"**: Attach sample data to entities to explain the data's purpose.
|
|
12
18
|
- **Interactive Layout**: Arrange entities via drag-and-drop; positions are saved directly back to your YAML.
|
|
13
19
|
- **Multi-file Support**: Manage multiple models in a single directory and switch between them seamlessly.
|
|
20
|
+
- **Documentation Export**: Generate Mermaid-compatible Markdown documentation including ER diagrams and domain catalogs.
|
|
14
21
|
- **AI-Agent Ready**: Scaffolding for Gemini, Claude, and Codex to help you model via AI.
|
|
15
22
|
|
|
16
23
|
## Installation
|
|
@@ -21,29 +28,79 @@ Install Modscape globally via npm:
|
|
|
21
28
|
npm install -g modscape
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Getting Started
|
|
34
|
+
|
|
35
|
+
Choose the path that fits your workflow.
|
|
36
|
+
|
|
37
|
+
### Path A: AI-Driven Modeling (Recommended)
|
|
38
|
+
Best if you use AI coding assistants (Gemini CLI, Claude Code, Cursor/Codex).
|
|
39
|
+
|
|
40
|
+
1. **Initialize**: Scaffold modeling rules and AI agent instructions.
|
|
41
|
+
```bash
|
|
42
|
+
modscape init
|
|
43
|
+
```
|
|
44
|
+
2. **Start Dev**: Launch the visualizer on your model.
|
|
45
|
+
```bash
|
|
46
|
+
modscape dev model.yaml
|
|
47
|
+
```
|
|
48
|
+
3. **Prompt Your AI**: Tell your agent to use the rules in `.modscape/rules.md` to add tables or columns to your `model.yaml`.
|
|
49
|
+
|
|
50
|
+
### Path B: Manual Modeling
|
|
51
|
+
Best for direct YAML editing and architectural control.
|
|
52
|
+
|
|
53
|
+
1. **Create YAML**: Create a file named `model.yaml` (see [Defining Your Model](#defining-your-model-yaml)).
|
|
54
|
+
2. **Start Dev**: Launch the visualizer.
|
|
55
|
+
```bash
|
|
56
|
+
modscape dev model.yaml
|
|
57
|
+
```
|
|
58
|
+
3. **Explore Samples**: Try it out with our built-in samples:
|
|
59
|
+
```bash
|
|
60
|
+
# Clone the repo or download the samples directory
|
|
61
|
+
modscape dev samples/
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Defining Your Model (YAML)
|
|
67
|
+
|
|
68
|
+
Modscape uses a human-readable YAML schema. While you can write it manually, we **highly recommend using an AI coding assistant** (like Gemini, Claude, or Cursor) to handle the boilerplate.
|
|
69
|
+
|
|
70
|
+
### Manual Definition Reference
|
|
71
|
+
Here is the basic structure for your `model.yaml`:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
tables:
|
|
75
|
+
- id: users
|
|
76
|
+
name: USERS
|
|
77
|
+
appearance:
|
|
78
|
+
type: dimension # dimension | fact | hub | link | satellite
|
|
79
|
+
icon: 👤
|
|
80
|
+
columns:
|
|
81
|
+
- id: user_id
|
|
82
|
+
logical: { name: USER_ID, type: UUID, isPrimaryKey: true }
|
|
83
|
+
- id: email
|
|
84
|
+
logical: { name: EMAIL, type: String }
|
|
85
|
+
|
|
86
|
+
# Data Vault Example
|
|
87
|
+
- id: hub_customer
|
|
88
|
+
name: HUB_CUSTOMER
|
|
89
|
+
appearance: { type: hub, icon: 🔑 }
|
|
90
|
+
columns:
|
|
91
|
+
- id: hk_customer
|
|
92
|
+
logical: { name: HK_CUSTOMER, type: Binary, isPrimaryKey: true }
|
|
93
|
+
- id: customer_id
|
|
94
|
+
logical: { name: CUSTOMER_ID, type: String }
|
|
31
95
|
```
|
|
32
96
|
|
|
33
|
-
|
|
34
|
-
|
|
97
|
+
- **Appearance Types**:
|
|
98
|
+
- `dimension` / `fact`: For Star Schema / Dimensional modeling.
|
|
99
|
+
- `hub` / `link` / `satellite`: For Data Vault 2.0 modeling.
|
|
100
|
+
- **IDs**: Use lowercase, snake_case for `id` fields (used for internal linking).
|
|
101
|
+
- **Layout**: Don't worry about the `layout:` section. Modscape will automatically add and update coordinates when you drag entities in the browser.
|
|
35
102
|
|
|
36
|
-
|
|
37
|
-
# Clone the repo or download the samples directory to try this:
|
|
38
|
-
modscape dev samples/
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 3. Start Modeling
|
|
42
|
-
Create a `model.yaml` and launch the interactive visualizer.
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
modscape dev model.yaml
|
|
46
|
-
```
|
|
103
|
+
---
|
|
47
104
|
|
|
48
105
|
## Usage
|
|
49
106
|
|
|
@@ -68,6 +125,17 @@ Generate a standalone static website to share your documentation (perfect for Gi
|
|
|
68
125
|
modscape build models/ -o dist-site
|
|
69
126
|
```
|
|
70
127
|
|
|
128
|
+
### Export Mode (Static Documentation)
|
|
129
|
+
Generate a comprehensive Markdown document with embedded Mermaid diagrams.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Print to stdout
|
|
133
|
+
modscape export models/ecommerce.yaml
|
|
134
|
+
|
|
135
|
+
# Save to a file
|
|
136
|
+
modscape export models/ -o docs/
|
|
137
|
+
```
|
|
138
|
+
|
|
71
139
|
## AI Agent Integration
|
|
72
140
|
|
|
73
141
|
Modscape is designed to work alongside AI coding assistants. By running `modscape init`, you get:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modscape",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Modscape: A YAML-driven data modeling visualizer CLI",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"src",
|
|
15
15
|
"visualizer-dist",
|
|
16
|
+
"visualizer/public/favicon.svg",
|
|
16
17
|
"package.json",
|
|
17
18
|
"README.md"
|
|
18
19
|
],
|
package/src/export.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes the schema data to ensure it's in a consistent format.
|
|
7
|
+
* Similar to visualizer/src/lib/parser.ts but for the CLI side.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeSchema(data) {
|
|
10
|
+
if (!data || typeof data !== 'object') {
|
|
11
|
+
throw new Error('Invalid YAML: Root must be an object');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const schema = {
|
|
15
|
+
tables: Array.isArray(data.tables) ? data.tables : [],
|
|
16
|
+
relationships: Array.isArray(data.relationships) ? data.relationships : [],
|
|
17
|
+
domains: Array.isArray(data.domains) ? data.domains : [],
|
|
18
|
+
layout: data.layout || {}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
schema.tables = schema.tables.map(table => ({
|
|
22
|
+
...table,
|
|
23
|
+
id: table.id || 'unknown',
|
|
24
|
+
name: table.name || table.id || 'Unnamed Table',
|
|
25
|
+
columns: Array.isArray(table.columns) ? table.columns : []
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
return schema;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Sanitizes a string for use as a Mermaid entity name.
|
|
33
|
+
* Spaces and hyphens are replaced with underscores.
|
|
34
|
+
*/
|
|
35
|
+
function sanitize(str) {
|
|
36
|
+
return str.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generates Mermaid ER diagram syntax.
|
|
41
|
+
*/
|
|
42
|
+
function generateMermaidER(schema) {
|
|
43
|
+
let mermaid = 'erDiagram\n';
|
|
44
|
+
|
|
45
|
+
// Tables (Entities)
|
|
46
|
+
schema.tables.forEach(table => {
|
|
47
|
+
const tableName = sanitize(table.name);
|
|
48
|
+
mermaid += ` ${tableName} {\n`;
|
|
49
|
+
table.columns.forEach(col => {
|
|
50
|
+
const colName = sanitize(col.logical?.name || col.id);
|
|
51
|
+
const colType = sanitize(col.logical?.type || 'string');
|
|
52
|
+
let markers = '';
|
|
53
|
+
if (col.logical?.isPrimaryKey) markers += ' PK';
|
|
54
|
+
if (col.logical?.isForeignKey) markers += ' FK';
|
|
55
|
+
mermaid += ` ${colType} ${colName}${markers}\n`;
|
|
56
|
+
});
|
|
57
|
+
mermaid += ' }\n';
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Relationships
|
|
61
|
+
schema.relationships.forEach(rel => {
|
|
62
|
+
const fromTable = sanitize(schema.tables.find(t => t.id === rel.from.table)?.name || rel.from.table);
|
|
63
|
+
const toTable = sanitize(schema.tables.find(t => t.id === rel.to.table)?.name || rel.to.table);
|
|
64
|
+
|
|
65
|
+
// Default to one-to-many if type is missing
|
|
66
|
+
let connector = '||--o{';
|
|
67
|
+
if (rel.type === 'one-to-one') connector = '||--||';
|
|
68
|
+
if (rel.type === 'many-to-many') connector = '}|--|{';
|
|
69
|
+
if (rel.type === 'one-to-many') connector = '||--o{';
|
|
70
|
+
|
|
71
|
+
mermaid += ` ${fromTable} ${connector} ${toTable} : ""\n`;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return mermaid;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generates the full Markdown document.
|
|
79
|
+
*/
|
|
80
|
+
export function generateMarkdown(schema, modelName) {
|
|
81
|
+
let md = `# ${modelName} Data Model Definition\n\n`;
|
|
82
|
+
|
|
83
|
+
// Mermaid ER Diagram
|
|
84
|
+
md += '## ER Diagram\n\n';
|
|
85
|
+
md += '```mermaid\n';
|
|
86
|
+
md += generateMermaidER(schema);
|
|
87
|
+
md += '```\n\n';
|
|
88
|
+
|
|
89
|
+
// Domains
|
|
90
|
+
if (schema.domains && schema.domains.length > 0) {
|
|
91
|
+
md += '## Domains\n\n';
|
|
92
|
+
schema.domains.forEach(domain => {
|
|
93
|
+
md += `### ${domain.name}\n`;
|
|
94
|
+
if (domain.description) md += `${domain.description}\n\n`;
|
|
95
|
+
md += 'Associated Tables:\n';
|
|
96
|
+
domain.tables.forEach(tableId => {
|
|
97
|
+
const table = schema.tables.find(t => t.id === tableId);
|
|
98
|
+
md += `- ${table?.name || tableId}\n`;
|
|
99
|
+
});
|
|
100
|
+
md += '\n';
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Table Catalog
|
|
105
|
+
md += '## Table Catalog\n\n';
|
|
106
|
+
schema.tables.forEach(table => {
|
|
107
|
+
md += `### ${table.name} ${table.appearance?.icon || ''}\n`;
|
|
108
|
+
if (table.conceptual?.description) {
|
|
109
|
+
md += `**Description**: ${table.conceptual.description}\n\n`;
|
|
110
|
+
}
|
|
111
|
+
if (table.appearance?.type) {
|
|
112
|
+
md += `**Type**: ${table.appearance.type}\n\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Columns Table
|
|
116
|
+
if (table.columns && table.columns.length > 0) {
|
|
117
|
+
md += '#### Column Definitions\n\n';
|
|
118
|
+
md += '| ID | Logical Name | Type | Key | Description |\n';
|
|
119
|
+
md += '| --- | --- | --- | --- | --- |\n';
|
|
120
|
+
table.columns.forEach(col => {
|
|
121
|
+
const key = [
|
|
122
|
+
col.logical?.isPrimaryKey ? 'PK' : '',
|
|
123
|
+
col.logical?.isForeignKey ? 'FK' : ''
|
|
124
|
+
].filter(Boolean).join(', ');
|
|
125
|
+
md += `| ${col.id} | ${col.logical?.name || '-'} | ${col.logical?.type || '-'} | ${key || '-'} | ${col.logical?.description || '-'} |\n`;
|
|
126
|
+
});
|
|
127
|
+
md += '\n';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Sample Data
|
|
131
|
+
if (table.sampleData && table.sampleData.rows && table.sampleData.rows.length > 0) {
|
|
132
|
+
md += '#### Sample Data\n\n';
|
|
133
|
+
md += '| ' + table.sampleData.columns.join(' | ') + ' |\n';
|
|
134
|
+
md += '| ' + table.sampleData.columns.map(() => '---').join(' | ') + ' |\n';
|
|
135
|
+
table.sampleData.rows.slice(0, 5).forEach(row => {
|
|
136
|
+
md += '| ' + row.join(' | ') + ' |\n';
|
|
137
|
+
});
|
|
138
|
+
if (table.sampleData.rows.length > 5) {
|
|
139
|
+
md += `\n*(Showing 5 of ${table.sampleData.rows.length} rows)*\n`;
|
|
140
|
+
}
|
|
141
|
+
md += '\n';
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return md;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Main export function called by the CLI
|
|
150
|
+
*/
|
|
151
|
+
export async function exportModel(paths, options) {
|
|
152
|
+
for (const inputPath of paths) {
|
|
153
|
+
const absolutePath = path.resolve(process.cwd(), inputPath);
|
|
154
|
+
if (!fs.existsSync(absolutePath)) {
|
|
155
|
+
console.warn(` ⚠️ Warning: Path not found: ${inputPath}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const stats = fs.statSync(absolutePath);
|
|
160
|
+
const files = stats.isDirectory()
|
|
161
|
+
? fs.readdirSync(absolutePath).filter(f => f.endsWith('.yaml') || f.endsWith('.yml')).map(f => path.join(absolutePath, f))
|
|
162
|
+
: [absolutePath];
|
|
163
|
+
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
try {
|
|
166
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
167
|
+
const data = yaml.load(content);
|
|
168
|
+
const schema = normalizeSchema(data);
|
|
169
|
+
const modelName = path.parse(file).name.charAt(0).toUpperCase() + path.parse(file).name.slice(1).replace(/[-_]/g, ' ');
|
|
170
|
+
const markdown = generateMarkdown(schema, modelName);
|
|
171
|
+
|
|
172
|
+
if (options.output) {
|
|
173
|
+
// If output is specified, we either append or use it as a directory/base
|
|
174
|
+
// For now, let's just write to the specified output file if it's one file,
|
|
175
|
+
// or treat as a directory if multiple.
|
|
176
|
+
let outputPath = options.output;
|
|
177
|
+
if (files.length > 1 || stats.isDirectory()) {
|
|
178
|
+
// ensure directory exists
|
|
179
|
+
if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
|
|
180
|
+
outputPath = path.join(outputPath, `${path.parse(file).name}.md`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fs.writeFileSync(outputPath, markdown, 'utf8');
|
|
184
|
+
console.log(` ✅ Exported: ${path.relative(process.cwd(), file)} -> ${outputPath}`);
|
|
185
|
+
} else {
|
|
186
|
+
process.stdout.write(markdown + '\n');
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error(` ❌ Error exporting ${file}: ${e.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { startDevServer } from './dev.js';
|
|
7
7
|
import { build } from './build.js';
|
|
8
8
|
import { initProject } from './init.js';
|
|
9
|
+
import { exportModel } from './export.js';
|
|
9
10
|
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const VISUALIZER_PATH = path.resolve(__dirname, '../visualizer');
|
|
@@ -14,7 +15,7 @@ const program = new Command();
|
|
|
14
15
|
program
|
|
15
16
|
.name('modscape')
|
|
16
17
|
.description('A YAML-driven data modeling visualizer CLI')
|
|
17
|
-
.version('0.
|
|
18
|
+
.version('0.3.0');
|
|
18
19
|
|
|
19
20
|
program
|
|
20
21
|
.command('init')
|
|
@@ -44,4 +45,13 @@ program
|
|
|
44
45
|
build(paths, VISUALIZER_PATH, options.output);
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
program
|
|
49
|
+
.command('export')
|
|
50
|
+
.description('Export YAML models to Mermaid-compatible Markdown documentation')
|
|
51
|
+
.argument('<paths...>', 'paths to YAML model files or directories')
|
|
52
|
+
.option('-o, --output <path>', 'output file or directory')
|
|
53
|
+
.action((paths, options) => {
|
|
54
|
+
exportModel(paths, options);
|
|
55
|
+
});
|
|
56
|
+
|
|
47
57
|
program.parse();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "visualizer",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
14
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
15
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
16
|
+
"class-variance-authority": "^0.7.1",
|
|
17
|
+
"clsx": "^2.1.1",
|
|
18
|
+
"js-yaml": "^4.1.1",
|
|
19
|
+
"lucide-react": "^0.575.0",
|
|
20
|
+
"react": "^19.2.0",
|
|
21
|
+
"react-dom": "^19.2.0",
|
|
22
|
+
"reactflow": "^11.11.4",
|
|
23
|
+
"tailwind-merge": "^3.5.0",
|
|
24
|
+
"zustand": "^5.0.11"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@eslint/js": "^9.39.1",
|
|
28
|
+
"@types/js-yaml": "^4.0.9",
|
|
29
|
+
"@types/node": "^24.10.1",
|
|
30
|
+
"@types/react": "^19.2.7",
|
|
31
|
+
"@types/react-dom": "^19.2.3",
|
|
32
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
33
|
+
"autoprefixer": "^10.4.27",
|
|
34
|
+
"eslint": "^9.39.1",
|
|
35
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
36
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
37
|
+
"globals": "^16.5.0",
|
|
38
|
+
"postcss": "^8.5.6",
|
|
39
|
+
"tailwindcss": "^3.4.19",
|
|
40
|
+
"typescript": "~5.9.3",
|
|
41
|
+
"typescript-eslint": "^8.48.0",
|
|
42
|
+
"vite": "^7.3.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Background Round Rect -->
|
|
3
|
+
<rect width="512" height="512" rx="112" fill="#FFFFFF"/>
|
|
4
|
+
|
|
5
|
+
<!-- Relationship Lines (Connecting in M-shape) -->
|
|
6
|
+
<path d="M120 350 L120 160 L256 310 L392 160 L392 350"
|
|
7
|
+
stroke="#CBD5E1" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
|
|
9
|
+
<!-- Colorful Rounded Rect Nodes -->
|
|
10
|
+
<!-- Left Bottom (Satellite) -->
|
|
11
|
+
<rect x="80" y="310" width="80" height="80" rx="16" fill="#8b5cf6"/>
|
|
12
|
+
|
|
13
|
+
<!-- Left Top (Hub) -->
|
|
14
|
+
<rect x="80" y="120" width="80" height="80" rx="16" fill="#f59e0b"/>
|
|
15
|
+
|
|
16
|
+
<!-- Center "V" (Fact) -->
|
|
17
|
+
<rect x="216" y="270" width="80" height="80" rx="16" fill="#ef4444"/>
|
|
18
|
+
|
|
19
|
+
<!-- Right Top (Link) -->
|
|
20
|
+
<rect x="352" y="120" width="80" height="80" rx="16" fill="#10b981"/>
|
|
21
|
+
|
|
22
|
+
<!-- Right Bottom (Dimension) -->
|
|
23
|
+
<rect x="352" y="310" width="80" height="80" rx="16" fill="#3b82f6"/>
|
|
24
|
+
</svg>
|