grg-kit-cli 0.3.3 ā 0.4.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/commands/add.js +10 -5
- package/commands/list.js +7 -1
- package/config/catalog-fetcher.js +198 -0
- package/config/resources.js +43 -17
- package/package.json +1 -1
- package/scripts/README.md +203 -71
- package/scripts/generate-resources.js +201 -72
package/commands/add.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const degit = require('degit');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const ora = require('ora');
|
|
4
|
-
const {
|
|
4
|
+
const { fetchCatalog, REPO } = require('../config/catalog-fetcher');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Add command - downloads blocks
|
|
@@ -12,6 +12,11 @@ const { RESOURCES, REPO } = require('../config/resources');
|
|
|
12
12
|
* grg add block --all
|
|
13
13
|
*/
|
|
14
14
|
async function add(options) {
|
|
15
|
+
// Fetch catalog dynamically (with caching)
|
|
16
|
+
const spinner = ora('Fetching catalog...').start();
|
|
17
|
+
const RESOURCES = await fetchCatalog({ silent: true });
|
|
18
|
+
spinner.stop();
|
|
19
|
+
|
|
15
20
|
// Determine which blocks to add
|
|
16
21
|
const blocksToAdd = [];
|
|
17
22
|
|
|
@@ -44,14 +49,14 @@ async function add(options) {
|
|
|
44
49
|
|
|
45
50
|
console.log(chalk.bold.cyan(`\nš¦ Adding ${blocksToAdd.length} block(s)\n`));
|
|
46
51
|
|
|
47
|
-
const
|
|
52
|
+
const downloadSpinner = ora();
|
|
48
53
|
|
|
49
54
|
for (const block of blocksToAdd) {
|
|
50
55
|
const outputPath = options.output
|
|
51
56
|
? `${options.output}/${block.name}`
|
|
52
57
|
: block.defaultOutput;
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
downloadSpinner.start(`Downloading ${block.title}...`);
|
|
55
60
|
|
|
56
61
|
try {
|
|
57
62
|
const emitter = degit(`${REPO}/${block.path}`, {
|
|
@@ -62,7 +67,7 @@ async function add(options) {
|
|
|
62
67
|
|
|
63
68
|
await emitter.clone(outputPath);
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
downloadSpinner.succeed(chalk.green(`ā ${block.title} added`));
|
|
66
71
|
console.log(chalk.gray(` Location: ${outputPath}`));
|
|
67
72
|
|
|
68
73
|
// Show dependencies if any
|
|
@@ -72,7 +77,7 @@ async function add(options) {
|
|
|
72
77
|
console.log();
|
|
73
78
|
|
|
74
79
|
} catch (error) {
|
|
75
|
-
|
|
80
|
+
downloadSpinner.fail(chalk.red(`Failed to download ${block.title}`));
|
|
76
81
|
console.error(chalk.red(error.message));
|
|
77
82
|
}
|
|
78
83
|
}
|
package/commands/list.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const { fetchCatalog } = require('../config/catalog-fetcher');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* List command - displays available blocks and themes
|
|
6
7
|
* Usage: grg list [category]
|
|
7
8
|
*/
|
|
8
9
|
async function list(category) {
|
|
10
|
+
// Fetch catalog dynamically
|
|
11
|
+
const spinner = ora('Fetching catalog...').start();
|
|
12
|
+
const RESOURCES = await fetchCatalog({ silent: true });
|
|
13
|
+
spinner.stop();
|
|
14
|
+
|
|
9
15
|
if (!category) {
|
|
10
16
|
// Show overview
|
|
11
17
|
console.log(chalk.bold.cyan('\nš¦ GRG Kit Resources\n'));
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic catalog fetcher with caching
|
|
3
|
+
* Fetches catalog.json from GitHub with fallback to static resources
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const https = require('https');
|
|
9
|
+
|
|
10
|
+
const REPO = 'Genesis-Research/grg-kit';
|
|
11
|
+
const CATALOG_URL = `https://raw.githubusercontent.com/${REPO}/main/templates/catalog.json`;
|
|
12
|
+
const CACHE_FILE = path.join(__dirname, '.catalog-cache.json');
|
|
13
|
+
const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
14
|
+
|
|
15
|
+
// In-memory cache for current session
|
|
16
|
+
let memoryCache = null;
|
|
17
|
+
let memoryCacheTime = 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch JSON from URL
|
|
21
|
+
*/
|
|
22
|
+
function fetchJson(url) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
https.get(url, (res) => {
|
|
25
|
+
if (res.statusCode !== 200) {
|
|
26
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let data = '';
|
|
31
|
+
res.on('data', chunk => data += chunk);
|
|
32
|
+
res.on('end', () => {
|
|
33
|
+
try {
|
|
34
|
+
resolve(JSON.parse(data));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
reject(new Error('Invalid JSON'));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}).on('error', reject);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read file cache
|
|
45
|
+
*/
|
|
46
|
+
function readFileCache() {
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
49
|
+
const cached = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
50
|
+
if (Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
51
|
+
return cached.data;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Cache read failed, ignore
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write file cache
|
|
62
|
+
*/
|
|
63
|
+
function writeFileCache(data) {
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
data
|
|
68
|
+
}), 'utf-8');
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Cache write failed, ignore
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get static fallback resources
|
|
76
|
+
*/
|
|
77
|
+
function getStaticFallback() {
|
|
78
|
+
try {
|
|
79
|
+
const { RESOURCES } = require('./resources');
|
|
80
|
+
return RESOURCES;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return { themes: [], components: [], blocks: [] };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fetch catalog with caching
|
|
88
|
+
* Priority: memory cache -> file cache -> network -> static fallback
|
|
89
|
+
*/
|
|
90
|
+
async function fetchCatalog(options = {}) {
|
|
91
|
+
const { forceRefresh = false, silent = false } = options;
|
|
92
|
+
|
|
93
|
+
// Check memory cache first (fastest)
|
|
94
|
+
if (!forceRefresh && memoryCache && (Date.now() - memoryCacheTime < CACHE_TTL_MS)) {
|
|
95
|
+
return memoryCache;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check file cache
|
|
99
|
+
if (!forceRefresh) {
|
|
100
|
+
const fileCached = readFileCache();
|
|
101
|
+
if (fileCached) {
|
|
102
|
+
memoryCache = fileCached;
|
|
103
|
+
memoryCacheTime = Date.now();
|
|
104
|
+
return fileCached;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fetch from network
|
|
109
|
+
try {
|
|
110
|
+
const catalog = await fetchJson(CATALOG_URL);
|
|
111
|
+
|
|
112
|
+
// Transform to match expected format
|
|
113
|
+
const resources = {
|
|
114
|
+
themes: catalog.themes.map(t => ({
|
|
115
|
+
...t,
|
|
116
|
+
path: `templates/ui/themes/${t.file}`,
|
|
117
|
+
defaultOutput: `src/themes/${t.file}`
|
|
118
|
+
})),
|
|
119
|
+
components: catalog.components.map(c => ({
|
|
120
|
+
...c,
|
|
121
|
+
path: `templates/ui/components/${c.name}`,
|
|
122
|
+
defaultOutput: `src/app/components/${c.name}`
|
|
123
|
+
})),
|
|
124
|
+
blocks: catalog.blocks.map(b => ({
|
|
125
|
+
...b,
|
|
126
|
+
path: `templates/ui/blocks/${b.name}`,
|
|
127
|
+
defaultOutput: `src/app/blocks/${b.name}`
|
|
128
|
+
}))
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Update caches
|
|
132
|
+
memoryCache = resources;
|
|
133
|
+
memoryCacheTime = Date.now();
|
|
134
|
+
writeFileCache(resources);
|
|
135
|
+
|
|
136
|
+
return resources;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (!silent) {
|
|
139
|
+
console.warn(`Warning: Could not fetch catalog (${error.message}), using cached/static resources`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Try file cache even if expired
|
|
143
|
+
try {
|
|
144
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
145
|
+
const cached = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
146
|
+
return cached.data;
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Ignore
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Final fallback to static resources
|
|
153
|
+
return getStaticFallback();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get resources synchronously (uses cache or static fallback)
|
|
159
|
+
*/
|
|
160
|
+
function getResourcesSync() {
|
|
161
|
+
// Check memory cache
|
|
162
|
+
if (memoryCache && (Date.now() - memoryCacheTime < CACHE_TTL_MS)) {
|
|
163
|
+
return memoryCache;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check file cache
|
|
167
|
+
const fileCached = readFileCache();
|
|
168
|
+
if (fileCached) {
|
|
169
|
+
memoryCache = fileCached;
|
|
170
|
+
memoryCacheTime = Date.now();
|
|
171
|
+
return fileCached;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Static fallback
|
|
175
|
+
return getStaticFallback();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Clear all caches
|
|
180
|
+
*/
|
|
181
|
+
function clearCache() {
|
|
182
|
+
memoryCache = null;
|
|
183
|
+
memoryCacheTime = 0;
|
|
184
|
+
try {
|
|
185
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
186
|
+
fs.unlinkSync(CACHE_FILE);
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
// Ignore
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
fetchCatalog,
|
|
195
|
+
getResourcesSync,
|
|
196
|
+
clearCache,
|
|
197
|
+
REPO
|
|
198
|
+
};
|
package/config/resources.js
CHANGED
|
@@ -133,11 +133,18 @@ const RESOURCES = {
|
|
|
133
133
|
{
|
|
134
134
|
"name": "file-upload",
|
|
135
135
|
"title": "File Upload Component",
|
|
136
|
-
"description": "file
|
|
136
|
+
"description": "Drag and drop file upload component",
|
|
137
137
|
"path": "templates/ui/components/file-upload",
|
|
138
138
|
"defaultOutput": "src/app/components/file-upload",
|
|
139
|
-
"tags": [
|
|
140
|
-
|
|
139
|
+
"tags": [
|
|
140
|
+
"file",
|
|
141
|
+
"upload",
|
|
142
|
+
"form",
|
|
143
|
+
"drag-drop"
|
|
144
|
+
],
|
|
145
|
+
"dependencies": [
|
|
146
|
+
"@spartan-ng/helm/button"
|
|
147
|
+
]
|
|
141
148
|
},
|
|
142
149
|
{
|
|
143
150
|
"name": "stepper",
|
|
@@ -154,23 +161,22 @@ const RESOURCES = {
|
|
|
154
161
|
"dependencies": [
|
|
155
162
|
"@spartan-ng/helm/button",
|
|
156
163
|
"@spartan-ng/helm/card"
|
|
157
|
-
]
|
|
158
|
-
"type": "grg-component",
|
|
159
|
-
"prefix": "grg"
|
|
164
|
+
]
|
|
160
165
|
}
|
|
161
166
|
],
|
|
162
167
|
"blocks": [
|
|
163
168
|
{
|
|
164
169
|
"name": "auth",
|
|
165
|
-
"title": "Auth
|
|
166
|
-
"description": "Authentication pages
|
|
170
|
+
"title": "Auth",
|
|
171
|
+
"description": "Authentication pages (login, signup, forgot password)",
|
|
167
172
|
"path": "templates/ui/blocks/auth",
|
|
168
173
|
"defaultOutput": "src/app/blocks/auth",
|
|
169
174
|
"tags": [
|
|
170
175
|
"auth",
|
|
171
176
|
"login",
|
|
172
177
|
"signup",
|
|
173
|
-
"authentication"
|
|
178
|
+
"authentication",
|
|
179
|
+
"form"
|
|
174
180
|
],
|
|
175
181
|
"dependencies": [
|
|
176
182
|
"@spartan-ng/helm/button",
|
|
@@ -180,25 +186,45 @@ const RESOURCES = {
|
|
|
180
186
|
},
|
|
181
187
|
{
|
|
182
188
|
"name": "settings",
|
|
183
|
-
"title": "Settings
|
|
184
|
-
"description": "
|
|
189
|
+
"title": "Settings",
|
|
190
|
+
"description": "Settings pages: profile, notifications, security, danger zone",
|
|
185
191
|
"path": "templates/ui/blocks/settings",
|
|
186
192
|
"defaultOutput": "src/app/blocks/settings",
|
|
187
193
|
"tags": [
|
|
188
|
-
"settings"
|
|
194
|
+
"settings",
|
|
195
|
+
"preferences",
|
|
196
|
+
"account",
|
|
197
|
+
"profile",
|
|
198
|
+
"security"
|
|
189
199
|
],
|
|
190
|
-
"dependencies": [
|
|
200
|
+
"dependencies": [
|
|
201
|
+
"@spartan-ng/helm/button",
|
|
202
|
+
"@spartan-ng/helm/card",
|
|
203
|
+
"@spartan-ng/helm/form-field",
|
|
204
|
+
"@spartan-ng/helm/switch"
|
|
205
|
+
]
|
|
191
206
|
},
|
|
192
207
|
{
|
|
193
208
|
"name": "shell",
|
|
194
|
-
"title": "Shell
|
|
195
|
-
"description": "shell
|
|
209
|
+
"title": "Shell",
|
|
210
|
+
"description": "Application shell layouts: sidebar, topnav, collapsible - each with optional footer variant",
|
|
196
211
|
"path": "templates/ui/blocks/shell",
|
|
197
212
|
"defaultOutput": "src/app/blocks/shell",
|
|
198
213
|
"tags": [
|
|
199
|
-
"shell"
|
|
214
|
+
"shell",
|
|
215
|
+
"layout",
|
|
216
|
+
"sidebar",
|
|
217
|
+
"header",
|
|
218
|
+
"footer",
|
|
219
|
+
"navigation",
|
|
220
|
+
"topnav",
|
|
221
|
+
"collapsible"
|
|
200
222
|
],
|
|
201
|
-
"dependencies": [
|
|
223
|
+
"dependencies": [
|
|
224
|
+
"@spartan-ng/helm/button",
|
|
225
|
+
"@spartan-ng/helm/icon",
|
|
226
|
+
"@spartan-ng/helm/dropdown-menu"
|
|
227
|
+
]
|
|
202
228
|
}
|
|
203
229
|
]
|
|
204
230
|
};
|
package/package.json
CHANGED
package/scripts/README.md
CHANGED
|
@@ -1,101 +1,233 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Resource Generation Flow
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
GRG Kit uses a **two-stage generation pipeline** to ensure CLI and MCP server always have up-to-date resource definitions.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```
|
|
8
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
9
|
+
ā SOURCE OF TRUTH ā
|
|
10
|
+
ā ā
|
|
11
|
+
ā app/src/app/blocks/{block}/meta.json ā Block metadata ā
|
|
12
|
+
ā app/src/themes/meta.json ā Theme metadata ā
|
|
13
|
+
ā app/libs/grg-ui/{component}/meta.json ā Component metadata ā
|
|
14
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
15
|
+
ā
|
|
16
|
+
ā¼
|
|
17
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
18
|
+
ā STAGE 1: pnpm generate:sources (in app/) ā
|
|
19
|
+
ā ā
|
|
20
|
+
ā ⢠Transforms source components ā template files ā
|
|
21
|
+
ā ⢠Copies meta.json files ā templates/ directory ā
|
|
22
|
+
ā ⢠Generates generated-sources.ts for showcase app ā
|
|
23
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
24
|
+
ā
|
|
25
|
+
ā¼
|
|
26
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
27
|
+
ā TEMPLATES DIRECTORY ā
|
|
28
|
+
ā ā
|
|
29
|
+
ā templates/ui/blocks/{block}/meta.json ā Copied from app ā
|
|
30
|
+
ā templates/ui/blocks/{block}/*.component.ts ā Generated ā
|
|
31
|
+
ā templates/ui/themes/meta.json ā Copied from app ā
|
|
32
|
+
ā templates/ui/components/{comp}/meta.json ā Copied from app ā
|
|
33
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
34
|
+
ā
|
|
35
|
+
ā¼
|
|
36
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
37
|
+
ā STAGE 2: node scripts/generate-resources.js (in cli/) ā
|
|
38
|
+
ā ā
|
|
39
|
+
ā ⢠Scans templates/ directory ā
|
|
40
|
+
ā ⢠Reads meta.json files for metadata ā
|
|
41
|
+
ā ⢠Generates cli/config/resources.js (static fallback) ā
|
|
42
|
+
ā ⢠Generates templates/catalog.json (dynamic, fetched at runtime) ā
|
|
43
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
44
|
+
ā
|
|
45
|
+
ā¼
|
|
46
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
47
|
+
ā RUNTIME (CLI & MCP) ā
|
|
48
|
+
ā ā
|
|
49
|
+
ā 1. Check memory cache (instant) ā
|
|
50
|
+
ā 2. Check file cache (~1ms) ā
|
|
51
|
+
ā 3. Fetch catalog.json from GitHub (~100-200ms) ā
|
|
52
|
+
ā 4. Fallback to static resources.js ā
|
|
53
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
8
57
|
|
|
9
|
-
|
|
10
|
-
- Outdated resource lists
|
|
11
|
-
- Manual maintenance errors
|
|
12
|
-
- Mismatched paths
|
|
13
|
-
- Missing new resources
|
|
58
|
+
## Adding New Resources
|
|
14
59
|
|
|
15
|
-
###
|
|
60
|
+
### 1. Add a New Block
|
|
16
61
|
|
|
17
62
|
```bash
|
|
18
|
-
#
|
|
19
|
-
|
|
63
|
+
# 1. Create block component in app
|
|
64
|
+
app/src/app/blocks/my-block/my-block.component.ts
|
|
20
65
|
|
|
21
|
-
#
|
|
22
|
-
|
|
66
|
+
# 2. Add metadata
|
|
67
|
+
app/src/app/blocks/my-block/meta.json
|
|
23
68
|
```
|
|
24
69
|
|
|
25
|
-
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"description": "Description for AI and CLI",
|
|
73
|
+
"tags": ["keyword1", "keyword2", "searchable"],
|
|
74
|
+
"dependencies": ["@spartan-ng/helm/button", "@spartan-ng/helm/card"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
26
77
|
|
|
27
|
-
The script runs automatically before publishing:
|
|
28
78
|
```bash
|
|
29
|
-
|
|
79
|
+
# 3. Update generate-sources.js CONFIG.blocks.sources array
|
|
80
|
+
|
|
81
|
+
# 4. Run generation
|
|
82
|
+
cd app && pnpm generate:sources
|
|
83
|
+
cd ../cli && node scripts/generate-resources.js
|
|
84
|
+
|
|
85
|
+
# 5. Commit and push - CLI/MCP pick up changes automatically
|
|
30
86
|
```
|
|
31
87
|
|
|
32
|
-
###
|
|
88
|
+
### 2. Add a New Theme
|
|
33
89
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Layouts (`templates/ui/layouts/*/`)
|
|
38
|
-
- Examples (`templates/spartan-examples/components/(*)`)
|
|
90
|
+
```bash
|
|
91
|
+
# 1. Create theme CSS
|
|
92
|
+
app/src/themes/my-theme.css
|
|
39
93
|
|
|
40
|
-
2.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- Tags for searchability
|
|
44
|
-
- Dependencies (if applicable)
|
|
94
|
+
# 2. Add entry to themes meta.json
|
|
95
|
+
app/src/themes/meta.json
|
|
96
|
+
```
|
|
45
97
|
|
|
46
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"my-theme.css": {
|
|
101
|
+
"description": "My custom theme description",
|
|
102
|
+
"tags": ["custom", "dark", "modern"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
47
106
|
|
|
48
|
-
###
|
|
107
|
+
### 3. Add a New GRG Component
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 1. Create component in libs/grg-ui
|
|
111
|
+
app/libs/grg-ui/my-component/src/...
|
|
49
112
|
|
|
113
|
+
# 2. Add metadata
|
|
114
|
+
app/libs/grg-ui/my-component/meta.json
|
|
50
115
|
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Examples: 56
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Scripts
|
|
120
|
+
|
|
121
|
+
### `app/scripts/generate-sources.js`
|
|
122
|
+
|
|
123
|
+
Generates template files and copies metadata.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cd app
|
|
127
|
+
pnpm generate:sources
|
|
64
128
|
```
|
|
65
129
|
|
|
66
|
-
|
|
130
|
+
**What it does:**
|
|
131
|
+
- Transforms block components ā standalone template files
|
|
132
|
+
- Copies `meta.json` files to `templates/` directory
|
|
133
|
+
- Generates `generated-sources.ts` for showcase app
|
|
67
134
|
|
|
68
|
-
|
|
135
|
+
### `cli/scripts/generate-resources.js`
|
|
69
136
|
|
|
70
|
-
|
|
71
|
-
const THEME_METADATA = {
|
|
72
|
-
'grg-theme.css': {
|
|
73
|
-
description: 'Default theme with purple/orange accents',
|
|
74
|
-
tags: ['default', 'purple', 'orange', 'colorful']
|
|
75
|
-
}
|
|
76
|
-
};
|
|
137
|
+
Generates CLI resources and dynamic catalog.
|
|
77
138
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
tags: ['form', 'wizard'],
|
|
82
|
-
dependencies: ['@spartan-ng/helm/button']
|
|
83
|
-
}
|
|
84
|
-
};
|
|
139
|
+
```bash
|
|
140
|
+
cd cli
|
|
141
|
+
node scripts/generate-resources.js
|
|
85
142
|
```
|
|
86
143
|
|
|
87
|
-
|
|
144
|
+
**What it does:**
|
|
145
|
+
- Scans `templates/` directory
|
|
146
|
+
- Reads `meta.json` files for metadata
|
|
147
|
+
- Generates `cli/config/resources.js` (static fallback)
|
|
148
|
+
- Generates `templates/catalog.json` (dynamic catalog)
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Dynamic Catalog Fetching
|
|
153
|
+
|
|
154
|
+
CLI and MCP server fetch `catalog.json` from GitHub at runtime with caching:
|
|
155
|
+
|
|
156
|
+
| Cache Level | TTL | Speed |
|
|
157
|
+
|-------------|-----|-------|
|
|
158
|
+
| Memory cache | 15 min | <1ms |
|
|
159
|
+
| File cache | 15 min | ~1ms |
|
|
160
|
+
| GitHub fetch | - | ~100-200ms |
|
|
161
|
+
| Static fallback | - | <1ms |
|
|
162
|
+
|
|
163
|
+
**Benefits:**
|
|
164
|
+
- No CLI/MCP redeploy needed for new resources
|
|
165
|
+
- Changes propagate within 15 minutes
|
|
166
|
+
- Graceful fallback if network fails
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## File Structure
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
grg-kit/
|
|
174
|
+
āāā app/
|
|
175
|
+
ā āāā src/
|
|
176
|
+
ā ā āāā app/blocks/
|
|
177
|
+
ā ā ā āāā auth/
|
|
178
|
+
ā ā ā ā āāā meta.json ā Source metadata
|
|
179
|
+
ā ā ā ā āāā *.component.ts
|
|
180
|
+
ā ā ā āāā shell/
|
|
181
|
+
ā ā ā ā āāā meta.json
|
|
182
|
+
ā ā ā ā āāā *.component.ts
|
|
183
|
+
ā ā ā āāā settings/
|
|
184
|
+
ā ā ā āāā meta.json
|
|
185
|
+
ā ā ā āāā *.component.ts
|
|
186
|
+
ā ā āāā themes/
|
|
187
|
+
ā ā āāā meta.json ā All themes metadata
|
|
188
|
+
ā ā āāā *.css
|
|
189
|
+
ā āāā libs/grg-ui/
|
|
190
|
+
ā ā āāā stepper/meta.json
|
|
191
|
+
ā ā āāā file-upload/meta.json
|
|
192
|
+
ā āāā scripts/
|
|
193
|
+
ā āāā generate-sources.js ā Stage 1
|
|
194
|
+
ā
|
|
195
|
+
āāā cli/
|
|
196
|
+
ā āāā config/
|
|
197
|
+
ā ā āāā resources.js ā Generated (static fallback)
|
|
198
|
+
ā ā āāā catalog-fetcher.js ā Dynamic fetcher
|
|
199
|
+
ā āāā scripts/
|
|
200
|
+
ā āāā generate-resources.js ā Stage 2
|
|
201
|
+
ā
|
|
202
|
+
āāā mcp-server/
|
|
203
|
+
ā āāā src/
|
|
204
|
+
ā āāā index.ts
|
|
205
|
+
ā āāā catalog-fetcher.ts ā Dynamic fetcher
|
|
206
|
+
ā
|
|
207
|
+
āāā templates/
|
|
208
|
+
āāā catalog.json ā Generated (dynamic)
|
|
209
|
+
āāā ui/
|
|
210
|
+
āāā blocks/
|
|
211
|
+
ā āāā auth/
|
|
212
|
+
ā ā āāā meta.json ā Copied from app
|
|
213
|
+
ā ā āāā *.component.ts ā Generated
|
|
214
|
+
ā āāā ...
|
|
215
|
+
āāā themes/
|
|
216
|
+
ā āāā meta.json ā Copied from app
|
|
217
|
+
ā āāā *.css
|
|
218
|
+
āāā components/
|
|
219
|
+
āāā stepper/meta.json ā Copied from app
|
|
220
|
+
āāā file-upload/meta.json
|
|
221
|
+
```
|
|
88
222
|
|
|
89
|
-
|
|
90
|
-
ā
**No manual updates** - Add files to templates, run script
|
|
91
|
-
ā
**Type-safe** - Consistent structure for all resources
|
|
92
|
-
ā
**MCP-ready** - Structured metadata for LLM consumption
|
|
93
|
-
ā
**Auto-discovery** - New resources automatically included
|
|
223
|
+
---
|
|
94
224
|
|
|
95
|
-
|
|
225
|
+
## Quick Reference
|
|
96
226
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
227
|
+
| Task | Command |
|
|
228
|
+
|------|---------|
|
|
229
|
+
| Generate templates + copy meta | `cd app && pnpm generate:sources` |
|
|
230
|
+
| Generate resources + catalog | `cd cli && node scripts/generate-resources.js` |
|
|
231
|
+
| Full regeneration | Run both above |
|
|
232
|
+
| Test CLI | `cd cli && node bin/grg.js list` |
|
|
233
|
+
| Build MCP | `cd mcp-server && pnpm build` |
|
|
@@ -10,59 +10,48 @@ const path = require('path');
|
|
|
10
10
|
|
|
11
11
|
const TEMPLATES_DIR = path.join(__dirname, '../../templates');
|
|
12
12
|
const OUTPUT_FILE = path.join(__dirname, '../config/resources.js');
|
|
13
|
+
const CATALOG_FILE = path.join(TEMPLATES_DIR, 'catalog.json');
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
description: 'Minimal grayscale palette',
|
|
26
|
-
tags: ['minimal', 'grayscale', 'neutral', 'clean']
|
|
27
|
-
},
|
|
28
|
-
'modern-minimal.css': {
|
|
29
|
-
description: 'Contemporary minimal design',
|
|
30
|
-
tags: ['minimal', 'modern', 'contemporary', 'clean']
|
|
31
|
-
},
|
|
32
|
-
'amber-minimal.css': {
|
|
33
|
-
description: 'Warm amber accents',
|
|
34
|
-
tags: ['minimal', 'warm', 'amber', 'orange']
|
|
35
|
-
},
|
|
36
|
-
'mocks.css': {
|
|
37
|
-
description: 'Theme for mockups and prototypes',
|
|
38
|
-
tags: ['mockup', 'prototype', 'design']
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Component metadata
|
|
43
|
-
const COMPONENT_METADATA = {
|
|
44
|
-
'stepper': {
|
|
45
|
-
description: 'Multi-step form component with progress indicator',
|
|
46
|
-
tags: ['form', 'wizard', 'multi-step', 'progress'],
|
|
47
|
-
dependencies: ['@spartan-ng/helm/button', '@spartan-ng/helm/card'],
|
|
48
|
-
type: 'grg-component',
|
|
49
|
-
prefix: 'grg'
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Block metadata (formerly layouts)
|
|
54
|
-
const BLOCK_METADATA = {
|
|
55
|
-
'dashboard': {
|
|
56
|
-
description: 'Full dashboard layout with sidebar and header',
|
|
57
|
-
tags: ['dashboard', 'admin', 'sidebar', 'navigation'],
|
|
58
|
-
dependencies: ['@spartan-ng/helm/button', '@spartan-ng/helm/card', '@spartan-ng/helm/navigation-menu']
|
|
59
|
-
},
|
|
60
|
-
'auth': {
|
|
61
|
-
description: 'Authentication pages layout (login, signup, forgot password)',
|
|
62
|
-
tags: ['auth', 'login', 'signup', 'authentication'],
|
|
63
|
-
dependencies: ['@spartan-ng/helm/button', '@spartan-ng/helm/card', '@spartan-ng/helm/form-field']
|
|
15
|
+
/**
|
|
16
|
+
* Read meta.json from a directory if it exists
|
|
17
|
+
*/
|
|
18
|
+
function readMeta(dir) {
|
|
19
|
+
const metaPath = path.join(dir, 'meta.json');
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(metaPath)) {
|
|
22
|
+
return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn(`Warning: Could not read ${metaPath}`);
|
|
64
26
|
}
|
|
65
|
-
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default metadata generators
|
|
32
|
+
*/
|
|
33
|
+
function defaultBlockMeta(name) {
|
|
34
|
+
return {
|
|
35
|
+
description: `${name} block`,
|
|
36
|
+
tags: [name],
|
|
37
|
+
dependencies: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function defaultComponentMeta(name) {
|
|
42
|
+
return {
|
|
43
|
+
description: `${name} component`,
|
|
44
|
+
tags: [],
|
|
45
|
+
dependencies: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function defaultThemeMeta(name) {
|
|
50
|
+
return {
|
|
51
|
+
description: `${name} theme`,
|
|
52
|
+
tags: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
66
55
|
|
|
67
56
|
function scanDirectory(dir) {
|
|
68
57
|
try {
|
|
@@ -77,14 +66,14 @@ function generateThemes() {
|
|
|
77
66
|
const themesDir = path.join(TEMPLATES_DIR, 'ui/themes');
|
|
78
67
|
const files = scanDirectory(themesDir);
|
|
79
68
|
|
|
69
|
+
// Read themes meta.json (contains all theme metadata keyed by filename)
|
|
70
|
+
const themesMeta = readMeta(themesDir) || {};
|
|
71
|
+
|
|
80
72
|
return files
|
|
81
73
|
.filter(file => file.isFile() && file.name.endsWith('.css'))
|
|
82
74
|
.map(file => {
|
|
83
75
|
const name = file.name.replace('.css', '');
|
|
84
|
-
const metadata =
|
|
85
|
-
description: `${name} theme`,
|
|
86
|
-
tags: []
|
|
87
|
-
};
|
|
76
|
+
const metadata = themesMeta[file.name] || defaultThemeMeta(name);
|
|
88
77
|
|
|
89
78
|
return {
|
|
90
79
|
name,
|
|
@@ -93,7 +82,7 @@ function generateThemes() {
|
|
|
93
82
|
file: file.name,
|
|
94
83
|
path: `templates/ui/themes/${file.name}`,
|
|
95
84
|
defaultOutput: `src/themes/${file.name}`,
|
|
96
|
-
tags: metadata.tags,
|
|
85
|
+
tags: metadata.tags || [],
|
|
97
86
|
features: ['dark-mode', 'tailwind-v4', 'spartan-ng', 'oklch']
|
|
98
87
|
};
|
|
99
88
|
});
|
|
@@ -106,11 +95,8 @@ function generateComponents() {
|
|
|
106
95
|
return dirs
|
|
107
96
|
.filter(dir => dir.isDirectory())
|
|
108
97
|
.map(dir => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
tags: [],
|
|
112
|
-
dependencies: []
|
|
113
|
-
};
|
|
98
|
+
const componentDir = path.join(componentsDir, dir.name);
|
|
99
|
+
const metadata = readMeta(componentDir) || defaultComponentMeta(dir.name);
|
|
114
100
|
|
|
115
101
|
return {
|
|
116
102
|
name: dir.name,
|
|
@@ -126,27 +112,150 @@ function generateComponents() {
|
|
|
126
112
|
});
|
|
127
113
|
}
|
|
128
114
|
|
|
129
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Convert filename to readable title
|
|
117
|
+
* e.g., 'sidebar-shell-footer.component.ts' -> 'Sidebar Shell Footer'
|
|
118
|
+
*/
|
|
119
|
+
function fileToTitle(filename) {
|
|
120
|
+
return filename
|
|
121
|
+
.replace('.component.ts', '')
|
|
122
|
+
.split('-')
|
|
123
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
124
|
+
.join(' ');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert filename to id
|
|
129
|
+
* e.g., 'sidebar-shell-footer.component.ts' -> 'sidebar-footer'
|
|
130
|
+
*/
|
|
131
|
+
function fileToId(filename, blockName) {
|
|
132
|
+
const base = filename.replace('.component.ts', '');
|
|
133
|
+
// Remove block name prefix if present (e.g., 'sidebar-shell' -> 'sidebar')
|
|
134
|
+
return base.replace(`-${blockName}`, '').replace(`${blockName}-`, '');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Scan block directory for individual component files
|
|
139
|
+
*/
|
|
140
|
+
function scanBlockFiles(blockDir, blockName) {
|
|
141
|
+
const files = scanDirectory(blockDir);
|
|
142
|
+
|
|
143
|
+
return files
|
|
144
|
+
.filter(file => file.isFile() && file.name.endsWith('.component.ts'))
|
|
145
|
+
.map(file => {
|
|
146
|
+
const id = fileToId(file.name, blockName);
|
|
147
|
+
const title = fileToTitle(file.name);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
id,
|
|
151
|
+
file: file.name,
|
|
152
|
+
title,
|
|
153
|
+
description: title
|
|
154
|
+
};
|
|
155
|
+
})
|
|
156
|
+
.sort((a, b) => a.file.localeCompare(b.file));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function generateBlocks(includeFiles = false) {
|
|
130
160
|
const blocksDir = path.join(TEMPLATES_DIR, 'ui/blocks');
|
|
131
161
|
const dirs = scanDirectory(blocksDir);
|
|
132
162
|
|
|
133
163
|
return dirs
|
|
134
164
|
.filter(dir => dir.isDirectory())
|
|
135
165
|
.map(dir => {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
tags: [dir.name],
|
|
139
|
-
dependencies: []
|
|
140
|
-
};
|
|
166
|
+
const blockDir = path.join(blocksDir, dir.name);
|
|
167
|
+
const metadata = readMeta(blockDir) || defaultBlockMeta(dir.name);
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
const block = {
|
|
143
170
|
name: dir.name,
|
|
144
|
-
title: dir.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
171
|
+
title: dir.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
145
172
|
description: metadata.description,
|
|
146
173
|
path: `templates/ui/blocks/${dir.name}`,
|
|
147
174
|
defaultOutput: `src/app/blocks/${dir.name}`,
|
|
148
|
-
tags: metadata.tags,
|
|
149
|
-
dependencies: metadata.dependencies
|
|
175
|
+
tags: metadata.tags || [],
|
|
176
|
+
dependencies: metadata.dependencies || []
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Include file-level details for catalog
|
|
180
|
+
if (includeFiles) {
|
|
181
|
+
block.files = scanBlockFiles(blockDir, dir.name);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return block;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Generate catalog.json for themes (with features)
|
|
190
|
+
*/
|
|
191
|
+
function generateThemesForCatalog() {
|
|
192
|
+
const themesDir = path.join(TEMPLATES_DIR, 'ui/themes');
|
|
193
|
+
const files = scanDirectory(themesDir);
|
|
194
|
+
|
|
195
|
+
// Read themes meta.json
|
|
196
|
+
const themesMeta = readMeta(themesDir) || {};
|
|
197
|
+
|
|
198
|
+
return files
|
|
199
|
+
.filter(file => file.isFile() && file.name.endsWith('.css'))
|
|
200
|
+
.map(file => {
|
|
201
|
+
const name = file.name.replace('.css', '');
|
|
202
|
+
const metadata = themesMeta[file.name] || defaultThemeMeta(name);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
name,
|
|
206
|
+
title: name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
207
|
+
description: metadata.description,
|
|
208
|
+
file: file.name,
|
|
209
|
+
tags: metadata.tags || [],
|
|
210
|
+
features: ['dark-mode', 'tailwind-v4', 'spartan-ng', 'oklch']
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate catalog.json for components
|
|
217
|
+
*/
|
|
218
|
+
function generateComponentsForCatalog() {
|
|
219
|
+
const componentsDir = path.join(TEMPLATES_DIR, 'ui/components');
|
|
220
|
+
const dirs = scanDirectory(componentsDir);
|
|
221
|
+
|
|
222
|
+
return dirs
|
|
223
|
+
.filter(dir => dir.isDirectory())
|
|
224
|
+
.map(dir => {
|
|
225
|
+
const componentDir = path.join(componentsDir, dir.name);
|
|
226
|
+
const metadata = readMeta(componentDir) || defaultComponentMeta(dir.name);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
name: dir.name,
|
|
230
|
+
title: dir.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
231
|
+
description: metadata.description,
|
|
232
|
+
tags: metadata.tags || [],
|
|
233
|
+
dependencies: metadata.dependencies || []
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Generate catalog.json for blocks (with files)
|
|
240
|
+
*/
|
|
241
|
+
function generateBlocksForCatalog() {
|
|
242
|
+
const blocksDir = path.join(TEMPLATES_DIR, 'ui/blocks');
|
|
243
|
+
const dirs = scanDirectory(blocksDir);
|
|
244
|
+
|
|
245
|
+
return dirs
|
|
246
|
+
.filter(dir => dir.isDirectory())
|
|
247
|
+
.map(dir => {
|
|
248
|
+
const blockDir = path.join(blocksDir, dir.name);
|
|
249
|
+
const metadata = readMeta(blockDir) || defaultBlockMeta(dir.name);
|
|
250
|
+
const files = scanBlockFiles(blockDir, dir.name);
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
name: dir.name,
|
|
254
|
+
title: dir.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
255
|
+
description: metadata.description,
|
|
256
|
+
tags: metadata.tags || [],
|
|
257
|
+
dependencies: metadata.dependencies,
|
|
258
|
+
files
|
|
150
259
|
};
|
|
151
260
|
});
|
|
152
261
|
}
|
|
@@ -162,6 +271,7 @@ function generateResourcesFile() {
|
|
|
162
271
|
console.log(`ā Found ${components.length} components`);
|
|
163
272
|
console.log(`ā Found ${blocks.length} blocks`);
|
|
164
273
|
|
|
274
|
+
// Generate resources.js (static fallback for CLI)
|
|
165
275
|
const output = `/**
|
|
166
276
|
* Resource definitions for GRG Kit
|
|
167
277
|
* This file is auto-generated from templates directory
|
|
@@ -180,10 +290,29 @@ module.exports = { RESOURCES, REPO };
|
|
|
180
290
|
|
|
181
291
|
fs.writeFileSync(OUTPUT_FILE, output);
|
|
182
292
|
console.log(`\nā
Generated ${OUTPUT_FILE}`);
|
|
293
|
+
|
|
294
|
+
// Generate catalog.json (dynamic, fetched at runtime)
|
|
295
|
+
const catalogThemes = generateThemesForCatalog();
|
|
296
|
+
const catalogComponents = generateComponentsForCatalog();
|
|
297
|
+
const catalogBlocks = generateBlocksForCatalog();
|
|
298
|
+
|
|
299
|
+
const totalFiles = catalogBlocks.reduce((sum, b) => sum + (b.files?.length || 0), 0);
|
|
300
|
+
|
|
301
|
+
const catalog = {
|
|
302
|
+
version: '1.0.0',
|
|
303
|
+
lastUpdated: new Date().toISOString().split('T')[0],
|
|
304
|
+
themes: catalogThemes,
|
|
305
|
+
components: catalogComponents,
|
|
306
|
+
blocks: catalogBlocks
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
fs.writeFileSync(CATALOG_FILE, JSON.stringify(catalog, null, 2));
|
|
310
|
+
console.log(`ā
Generated ${CATALOG_FILE}`);
|
|
311
|
+
|
|
183
312
|
console.log('\nš¦ Resource Summary:');
|
|
184
313
|
console.log(` Themes: ${themes.length}`);
|
|
185
314
|
console.log(` Components: ${components.length}`);
|
|
186
|
-
console.log(` Blocks: ${blocks.length}`);
|
|
315
|
+
console.log(` Blocks: ${blocks.length} (${totalFiles} files)`);
|
|
187
316
|
}
|
|
188
317
|
|
|
189
318
|
// Run the generator
|