modscape 1.0.7 → 1.1.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.ja.md CHANGED
@@ -113,6 +113,13 @@ modscape dev ./models
113
113
  ```
114
114
  - **永続化**: レイアウトやメタデータの変更は、直接ファイルに書き戻されます(オートセーブ対応)。
115
115
 
116
+ ### 新規モデルの作成
117
+ ```bash
118
+ modscape new models/sales/customer.yaml
119
+ ```
120
+ - **再帰的作成**: 指定したパスの親ディレクトリが存在しない場合、自動的に作成します。
121
+ - **ボイラープレート**: ドメイン、3階層ネーミング、リレーション、リネージの例が含まれた有効なYAMLファイルを生成します。
122
+
116
123
  ### ビルドモード (静的サイト)
117
124
  ```bash
118
125
  modscape build ./models -o docs-site
package/README.md CHANGED
@@ -129,6 +129,13 @@ modscape dev ./models
129
129
  ```
130
130
  - **Persistence**: Layout and metadata changes are saved directly to your files (supports Auto-save).
131
131
 
132
+ ### Create New Model
133
+ ```bash
134
+ modscape new models/sales/customer.yaml
135
+ ```
136
+ - **Recursive Scaffolding**: Automatically creates parent directories if they don't exist.
137
+ - **Boilerplate**: Generates a valid YAML model with examples of domains, tri-layer naming, relationships, and lineage.
138
+
132
139
  ### Build Mode (Static Site)
133
140
  ```bash
134
141
  modscape build ./models -o docs-site
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modscape",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Modscape: A YAML-driven data modeling visualizer CLI",
5
5
  "repository": {
6
6
  "type": "git",
package/src/export.js CHANGED
@@ -15,6 +15,7 @@ function normalizeSchema(data) {
15
15
  tables: Array.isArray(data.tables) ? data.tables : [],
16
16
  relationships: Array.isArray(data.relationships) ? data.relationships : [],
17
17
  domains: Array.isArray(data.domains) ? data.domains : [],
18
+ annotations: Array.isArray(data.annotations) ? data.annotations : [],
18
19
  layout: data.layout || {}
19
20
  };
20
21
 
@@ -30,11 +31,18 @@ function normalizeSchema(data) {
30
31
  }
31
32
  }
32
33
 
34
+ const columns = (Array.isArray(table.columns) ? table.columns : []).map(col => ({
35
+ ...col,
36
+ id: col.id || 'unknown',
37
+ logical: col.logical || { name: col.id || 'unknown', type: 'string' },
38
+ physical: col.physical || { name: '', type: '' }
39
+ }));
40
+
33
41
  return {
34
42
  ...table,
35
43
  id: table.id || 'unknown',
36
44
  name: table.name || table.id || 'Unnamed Table',
37
- columns: Array.isArray(table.columns) ? table.columns : [],
45
+ columns,
38
46
  sampleData: Array.isArray(sampleData) ? sampleData : []
39
47
  };
40
48
  });
@@ -47,7 +55,31 @@ function normalizeSchema(data) {
47
55
  * Spaces and hyphens are replaced with underscores.
48
56
  */
49
57
  function sanitize(str) {
50
- return str.replace(/[^a-zA-Z0-9_]/g, '_');
58
+ if (!str) return 'unknown';
59
+ // Mermaid ER/Flowchart labels can be wrapped in quotes for better compatibility
60
+ return str.replace(/[^a-zA-Z0-9_\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/g, '_');
61
+ }
62
+
63
+ /**
64
+ * Generates Mermaid Data Lineage syntax.
65
+ */
66
+ function generateMermaidLineage(schema) {
67
+ let mermaid = 'graph TD\n';
68
+ let hasLineage = false;
69
+
70
+ schema.tables.forEach(table => {
71
+ if (table.lineage?.upstream && table.lineage.upstream.length > 0) {
72
+ hasLineage = true;
73
+ const targetName = sanitize(table.name);
74
+ table.lineage.upstream.forEach(upId => {
75
+ const sourceTable = schema.tables.find(t => t.id === upId);
76
+ const sourceName = sanitize(sourceTable?.name || upId);
77
+ mermaid += ` ${sourceName} --> ${targetName}\n`;
78
+ });
79
+ }
80
+ });
81
+
82
+ return hasLineage ? mermaid : null;
51
83
  }
52
84
 
53
85
  /**
@@ -88,6 +120,24 @@ function generateMermaidER(schema) {
88
120
  return mermaid;
89
121
  }
90
122
 
123
+ /**
124
+ * Generates the Sticky Notes section.
125
+ */
126
+ function generateNotesSection(schema) {
127
+ if (!schema.annotations || schema.annotations.length === 0) return null;
128
+
129
+ let md = '## Sticky Notes\n\n';
130
+ schema.annotations.forEach(note => {
131
+ md += `> **[Note]** ${note.text}\n`;
132
+ if (note.targetId) {
133
+ md += `> *Attached to: ${note.targetId} (${note.targetType})*\n`;
134
+ }
135
+ md += '\n';
136
+ });
137
+
138
+ return md;
139
+ }
140
+
91
141
  /**
92
142
  * Generates the full Markdown document.
93
143
  */
@@ -95,10 +145,21 @@ export function generateMarkdown(schema, modelName) {
95
145
  let md = `# ${modelName} Data Model Definition\n\n`;
96
146
 
97
147
  // Mermaid ER Diagram
98
- md += '## ER Diagram\n\n';
99
- md += '```mermaid\n';
100
- md += generateMermaidER(schema);
101
- md += '```\n\n';
148
+ if (schema.relationships && schema.relationships.length > 0) {
149
+ md += '## ER Diagram\n\n';
150
+ md += '```mermaid\n';
151
+ md += generateMermaidER(schema);
152
+ md += '```\n\n';
153
+ }
154
+
155
+ // Mermaid Data Lineage
156
+ const lineageMermaid = generateMermaidLineage(schema);
157
+ if (lineageMermaid) {
158
+ md += '## Data Lineage\n\n';
159
+ md += '```mermaid\n';
160
+ md += lineageMermaid;
161
+ md += '```\n\n';
162
+ }
102
163
 
103
164
  // Domains
104
165
  if (schema.domains && schema.domains.length > 0) {
@@ -123,20 +184,26 @@ export function generateMarkdown(schema, modelName) {
123
184
  md += `**Description**: ${table.conceptual.description}\n\n`;
124
185
  }
125
186
  if (table.appearance?.type) {
126
- md += `**Type**: ${table.appearance.type}\n\n`;
187
+ md += `**Type**: ${table.appearance.type.toUpperCase()}\n\n`;
127
188
  }
128
189
 
129
- // Columns Table
190
+ // Columns Table (Enhanced with Physical info)
130
191
  if (table.columns && table.columns.length > 0) {
131
192
  md += '#### Column Definitions\n\n';
132
- md += '| ID | Logical Name | Type | Key | Description |\n';
133
- md += '| --- | --- | --- | --- | --- |\n';
193
+ md += '| Logical Name | Physical Name | Logical Type | Physical Type | Key | Description |\n';
194
+ md += '| --- | --- | --- | --- | --- | --- |\n';
134
195
  table.columns.forEach(col => {
135
196
  const key = [
136
197
  col.logical?.isPrimaryKey ? 'PK' : '',
137
198
  col.logical?.isForeignKey ? 'FK' : ''
138
199
  ].filter(Boolean).join(', ');
139
- md += `| ${col.id} | ${col.logical?.name || '-'} | ${col.logical?.type || '-'} | ${key || '-'} | ${col.logical?.description || '-'} |\n`;
200
+ const logicalName = col.logical?.name || col.id;
201
+ const physicalName = col.physical?.name || '-';
202
+ const logicalType = col.logical?.type || '-';
203
+ const physicalType = col.physical?.type || '-';
204
+ const description = col.logical?.description || '-';
205
+
206
+ md += `| ${logicalName} | ${physicalName} | ${logicalType} | ${physicalType} | ${key || '-'} | ${description} |\n`;
140
207
  });
141
208
  md += '\n';
142
209
  }
@@ -157,6 +224,12 @@ export function generateMarkdown(schema, modelName) {
157
224
  }
158
225
  });
159
226
 
227
+ // Sticky Notes Section
228
+ const notesSection = generateNotesSection(schema);
229
+ if (notesSection) {
230
+ md += notesSection;
231
+ }
232
+
160
233
  return md;
161
234
  }
162
235
 
package/src/index.js CHANGED
@@ -16,7 +16,7 @@ const program = new Command();
16
16
  program
17
17
  .name('modscape')
18
18
  .description('Modscape: A YAML-driven data modeling visualizer CLI')
19
- .version('1.0.7');
19
+ .version('1.0.9');
20
20
 
21
21
  program
22
22
  .command('init')
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "visualizer",
3
3
  "private": true,
4
- "version": "1.0.7",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -20,6 +20,7 @@
20
20
  "clsx": "^2.1.1",
21
21
  "codemirror": "^6.0.2",
22
22
  "dagre": "^0.8.5",
23
+ "html-to-image": "^1.11.13",
23
24
  "js-yaml": "^4.1.1",
24
25
  "lucide-react": "^0.575.0",
25
26
  "react": "^19.2.0",