grg-kit-cli 0.6.2 → 0.6.3

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 CHANGED
@@ -21,9 +21,15 @@ grg init
21
21
  # Or with a specific theme
22
22
  grg init --theme claude
23
23
 
24
- # Add blocks
25
- grg add block --auth
26
- grg add block --shell
24
+ # Add blocks (all files)
25
+ grg add block auth
26
+ grg add block shell
27
+
28
+ # Add specific files from a block
29
+ grg add block auth login
30
+ grg add block shell sidebar
31
+
32
+ # Add all blocks
27
33
  grg add block --all
28
34
 
29
35
  # List available resources
@@ -73,24 +79,32 @@ Examples:
73
79
 
74
80
  ### `grg add block`
75
81
 
76
- Add blocks to your project.
82
+ Add blocks or specific block files to your project.
77
83
 
78
84
  ```bash
79
- grg add block [options]
85
+ grg add block <blockName> [fileIds...]
80
86
 
81
87
  Options:
82
- --all Add all blocks
83
- --auth Add authentication block (login, signup, forgot password)
84
- --shell Add app shell block (sidebar, header, content area)
85
- --settings Add settings block (settings page with sidebar navigation)
86
- -o, --output <path> Custom output directory
88
+ --all Add all blocks
89
+ -o, --output <path> Custom output directory
87
90
 
88
91
  Examples:
89
- grg add block --auth
90
- grg add block --shell --settings
91
- grg add block --all
92
+ grg add block auth # All auth files
93
+ grg add block auth login # Only login
94
+ grg add block auth login register # Login and register
95
+ grg add block shell sidebar # Only sidebar shell
96
+ grg add block shell topnav topnav-footer # Topnav variants
97
+ grg add block --all # All blocks
92
98
  ```
93
99
 
100
+ **Available blocks and files:**
101
+
102
+ | Block | Files |
103
+ |-------|-------|
104
+ | `auth` | `login`, `register`, `forgot-password` |
105
+ | `shell` | `sidebar`, `sidebar-footer`, `topnav`, `topnav-footer`, `collapsible`, `collapsible-footer` |
106
+ | `settings` | `profile`, `security`, `notification`, `danger-zone` |
107
+
94
108
  ### `grg list [category]`
95
109
 
96
110
  List available blocks and themes.
@@ -181,11 +195,14 @@ cd my-app
181
195
  grg init # Default theme
182
196
  grg init --theme claude # Custom theme
183
197
 
184
- # Add blocks
185
- grg add block --auth # Auth pages
186
- grg add block --shell # App shell
187
- grg add block --settings # Settings page
188
- grg add block --auth --shell # Multiple blocks
198
+ # Add blocks (all files)
199
+ grg add block auth # All auth files
200
+ grg add block shell # All shell layouts
201
+ grg add block settings # All settings pages
202
+
203
+ # Add specific files
204
+ grg add block auth login # Just login
205
+ grg add block shell sidebar # Just sidebar shell
189
206
  grg add block --all # All blocks
190
207
 
191
208
  # List resources
package/bin/grg.js CHANGED
@@ -24,12 +24,9 @@ program
24
24
  const addCommand = program.command('add').description('Add resources to your project');
25
25
 
26
26
  addCommand
27
- .command('block')
28
- .description('Add blocks to your project')
27
+ .command('block [blockName] [fileIds...]')
28
+ .description('Add blocks to your project (e.g., grg add block auth login)')
29
29
  .option('--all', 'Add all blocks')
30
- .option('--auth', 'Add authentication block (login, signup, forgot password)')
31
- .option('--shell', 'Add app shell block (sidebar, header, content area)')
32
- .option('--settings', 'Add settings block (settings page with sidebar navigation)')
33
30
  .option('-o, --output <path>', 'Custom output directory')
34
31
  .action(add);
35
32
 
package/commands/add.js CHANGED
@@ -1,59 +1,81 @@
1
1
  const degit = require('degit');
2
2
  const chalk = require('chalk');
3
3
  const ora = require('ora');
4
+ const fs = require('fs').promises;
5
+ const path = require('path');
6
+ const https = require('https');
4
7
  const { fetchCatalog, REPO } = require('../config/catalog-fetcher');
5
8
 
6
9
  /**
7
- * Add command - downloads blocks
8
- * Format: grg add block [options]
10
+ * Add command - downloads blocks or specific block files
11
+ * Format: grg add block <blockName> [fileIds...]
9
12
  * Examples:
10
- * grg add block --auth
11
- * grg add block --shell --settings
12
- * grg add block --all
13
+ * grg add block auth # All auth files
14
+ * grg add block auth login register # Only login and register
15
+ * grg add block shell sidebar # Only sidebar shell
16
+ * grg add block --all # All blocks with all files
13
17
  */
14
- async function add(options) {
18
+ async function add(blockName, fileIds, options) {
15
19
  // Fetch catalog dynamically (with caching)
16
20
  const spinner = ora('Fetching catalog...').start();
17
21
  const RESOURCES = await fetchCatalog({ silent: true });
18
22
  spinner.stop();
19
23
 
20
- // Determine which blocks to add
21
- const blocksToAdd = [];
22
-
24
+ // Handle --all flag (download everything)
23
25
  if (options.all) {
24
- blocksToAdd.push(...RESOURCES.blocks);
25
- } else {
26
- if (options.auth) {
27
- const block = RESOURCES.blocks.find(b => b.name === 'auth');
28
- if (block) blocksToAdd.push(block);
29
- }
30
- if (options.shell) {
31
- const block = RESOURCES.blocks.find(b => b.name === 'shell');
32
- if (block) blocksToAdd.push(block);
33
- }
34
- if (options.settings) {
35
- const block = RESOURCES.blocks.find(b => b.name === 'settings');
36
- if (block) blocksToAdd.push(block);
26
+ console.log(chalk.bold.cyan(`\n📦 Adding all blocks\n`));
27
+
28
+ for (const block of RESOURCES.blocks) {
29
+ await downloadBlock(block, null, options.output);
37
30
  }
31
+
32
+ console.log(chalk.bold.green('✨ Done!'));
33
+ return;
38
34
  }
39
35
 
40
- if (blocksToAdd.length === 0) {
41
- console.log(chalk.yellow('\nNo blocks specified. Use one of the following options:\n'));
42
- console.log(chalk.cyan(' grg add block --all'), chalk.gray(' Add all blocks'));
43
- console.log(chalk.cyan(' grg add block --auth'), chalk.gray(' Add authentication block'));
44
- console.log(chalk.cyan(' grg add block --shell'), chalk.gray(' Add app shell block'));
45
- console.log(chalk.cyan(' grg add block --settings'), chalk.gray(' Add settings block'));
46
- console.log(chalk.gray('\nRun'), chalk.cyan('grg list blocks'), chalk.gray('for more details'));
36
+ // Validate block name
37
+ if (!blockName) {
38
+ showUsage(RESOURCES);
47
39
  process.exit(1);
48
40
  }
49
41
 
50
- console.log(chalk.bold.cyan(`\n📦 Adding ${blocksToAdd.length} block(s)\n`));
42
+ const block = RESOURCES.blocks.find(b => b.name === blockName);
43
+ if (!block) {
44
+ console.error(chalk.red(`\nError: Block "${blockName}" not found`));
45
+ console.log(chalk.yellow('\nAvailable blocks:'));
46
+ RESOURCES.blocks.forEach(b => {
47
+ console.log(chalk.cyan(` ${b.name}`), chalk.gray(`- ${b.description}`));
48
+ });
49
+ process.exit(1);
50
+ }
51
51
 
52
- const downloadSpinner = ora();
52
+ // Validate file IDs if provided
53
+ if (fileIds && fileIds.length > 0) {
54
+ const invalidFiles = fileIds.filter(id => !block.files?.find(f => f.id === id));
55
+ if (invalidFiles.length > 0) {
56
+ console.error(chalk.red(`\nError: Invalid file(s) for ${blockName}: ${invalidFiles.join(', ')}`));
57
+ console.log(chalk.yellow('\nAvailable files:'));
58
+ block.files?.forEach(f => {
59
+ console.log(chalk.cyan(` ${f.id}`), chalk.gray(`- ${f.title}`));
60
+ });
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ await downloadBlock(block, fileIds, options.output);
66
+ console.log(chalk.bold.green('✨ Done!'));
67
+ }
53
68
 
54
- for (const block of blocksToAdd) {
55
- const outputPath = options.output
56
- ? `${options.output}/${block.name}`
69
+ /**
70
+ * Download a block (all files or specific files)
71
+ */
72
+ async function downloadBlock(block, fileIds, customOutput) {
73
+ const downloadSpinner = ora();
74
+
75
+ // If no specific files requested, or block has no files array, download entire block
76
+ if (!fileIds || fileIds.length === 0 || !block.files || block.files.length === 0) {
77
+ const outputPath = customOutput
78
+ ? `${customOutput}/${block.name}`
57
79
  : block.defaultOutput;
58
80
 
59
81
  downloadSpinner.start(`Downloading ${block.title}...`);
@@ -70,7 +92,6 @@ async function add(options) {
70
92
  downloadSpinner.succeed(chalk.green(`✓ ${block.title} added`));
71
93
  console.log(chalk.gray(` Location: ${outputPath}`));
72
94
 
73
- // Show dependencies if any
74
95
  if (block.dependencies && block.dependencies.length > 0) {
75
96
  console.log(chalk.gray(` Dependencies: ${block.dependencies.join(', ')}`));
76
97
  }
@@ -80,9 +101,99 @@ async function add(options) {
80
101
  downloadSpinner.fail(chalk.red(`Failed to download ${block.title}`));
81
102
  console.error(chalk.red(error.message));
82
103
  }
104
+ return;
83
105
  }
84
106
 
85
- console.log(chalk.bold.green('✨ Done!'));
107
+ // Download specific files
108
+ const filesToDownload = block.files.filter(f => fileIds.includes(f.id));
109
+ const outputDir = customOutput || block.defaultOutput;
110
+
111
+ console.log(chalk.bold.cyan(`\n📦 Adding ${filesToDownload.length} file(s) from ${block.title}\n`));
112
+
113
+ // Ensure output directory exists
114
+ await fs.mkdir(outputDir, { recursive: true });
115
+
116
+ for (const file of filesToDownload) {
117
+ downloadSpinner.start(`Downloading ${file.title}...`);
118
+
119
+ try {
120
+ const fileUrl = `https://raw.githubusercontent.com/${REPO}/main/${block.path}/${file.file}`;
121
+ const outputPath = path.join(outputDir, file.file);
122
+
123
+ await downloadFile(fileUrl, outputPath);
124
+
125
+ downloadSpinner.succeed(chalk.green(`✓ ${file.title} added`));
126
+ console.log(chalk.gray(` Location: ${outputPath}`));
127
+
128
+ } catch (error) {
129
+ downloadSpinner.fail(chalk.red(`Failed to download ${file.title}`));
130
+ console.error(chalk.red(error.message));
131
+ }
132
+ }
133
+
134
+ if (block.dependencies && block.dependencies.length > 0) {
135
+ console.log(chalk.gray(`\nDependencies: ${block.dependencies.join(', ')}`));
136
+ }
137
+ console.log();
138
+ }
139
+
140
+ /**
141
+ * Download a single file from URL
142
+ */
143
+ function downloadFile(url, outputPath) {
144
+ return new Promise((resolve, reject) => {
145
+ https.get(url, (res) => {
146
+ if (res.statusCode === 301 || res.statusCode === 302) {
147
+ // Follow redirect
148
+ https.get(res.headers.location, (res2) => {
149
+ handleResponse(res2, outputPath, resolve, reject);
150
+ }).on('error', reject);
151
+ } else {
152
+ handleResponse(res, outputPath, resolve, reject);
153
+ }
154
+ }).on('error', reject);
155
+ });
156
+ }
157
+
158
+ function handleResponse(res, outputPath, resolve, reject) {
159
+ if (res.statusCode !== 200) {
160
+ reject(new Error(`HTTP ${res.statusCode}`));
161
+ return;
162
+ }
163
+
164
+ let data = '';
165
+ res.on('data', chunk => data += chunk);
166
+ res.on('end', async () => {
167
+ try {
168
+ await fs.writeFile(outputPath, data, 'utf-8');
169
+ resolve();
170
+ } catch (e) {
171
+ reject(e);
172
+ }
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Show usage help
178
+ */
179
+ function showUsage(RESOURCES) {
180
+ console.log(chalk.yellow('\nUsage: grg add block <blockName> [fileIds...]\n'));
181
+ console.log(chalk.bold('Examples:'));
182
+ console.log(chalk.cyan(' grg add block auth'), chalk.gray(' # All auth files'));
183
+ console.log(chalk.cyan(' grg add block auth login'), chalk.gray(' # Only login'));
184
+ console.log(chalk.cyan(' grg add block auth login register'), chalk.gray(' # Login and register'));
185
+ console.log(chalk.cyan(' grg add block shell sidebar'), chalk.gray(' # Only sidebar shell'));
186
+ console.log(chalk.cyan(' grg add block --all'), chalk.gray(' # All blocks'));
187
+
188
+ console.log(chalk.bold('\nAvailable blocks:'));
189
+ RESOURCES.blocks.forEach(b => {
190
+ console.log(chalk.cyan(` ${b.name}`), chalk.gray(`- ${b.description}`));
191
+ if (b.files && b.files.length > 0) {
192
+ console.log(chalk.gray(` Files: ${b.files.map(f => f.id).join(', ')}`));
193
+ }
194
+ });
195
+
196
+ console.log(chalk.gray('\nRun'), chalk.cyan('grg list blocks'), chalk.gray('for more details'));
86
197
  }
87
198
 
88
199
  module.exports = { add };
@@ -0,0 +1 @@
1
+ {"timestamp":1765202627293,"data":{"themes":[{"name":"amber-minimal","title":"Amber Minimal","description":"Warm amber accents","file":"amber-minimal.css","tags":["minimal","warm","amber","orange"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/amber-minimal.css","defaultOutput":"src/themes/amber-minimal.css"},{"name":"claude","title":"Claude","description":"Claude-inspired warm tones","file":"claude.css","tags":["warm","orange","brown","claude"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/claude.css","defaultOutput":"src/themes/claude.css"},{"name":"clean-slate","title":"Clean Slate","description":"Minimal grayscale palette","file":"clean-slate.css","tags":["minimal","grayscale","neutral","clean"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/clean-slate.css","defaultOutput":"src/themes/clean-slate.css"},{"name":"grg-theme","title":"Grg Theme","description":"Default theme with purple/orange accents","file":"grg-theme.css","tags":["default","purple","orange","colorful"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/grg-theme.css","defaultOutput":"src/themes/grg-theme.css"},{"name":"mocks","title":"Mocks","description":"Theme for mockups and prototypes","file":"mocks.css","tags":["mockup","prototype","design"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/mocks.css","defaultOutput":"src/themes/mocks.css"},{"name":"modern-minimal","title":"Modern Minimal","description":"Contemporary minimal design","file":"modern-minimal.css","tags":["minimal","modern","contemporary","clean"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/modern-minimal.css","defaultOutput":"src/themes/modern-minimal.css"}],"components":[{"name":"file-upload","title":"File Upload","description":"Drag and drop file upload component","tags":["file","upload","form","drag-drop"],"dependencies":["@spartan-ng/helm/button"],"path":"templates/ui/components/file-upload","defaultOutput":"src/app/components/file-upload"},{"name":"stepper","title":"Stepper","description":"Multi-step form component with progress indicator","tags":["form","wizard","multi-step","progress"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/card"],"path":"templates/ui/components/stepper","defaultOutput":"src/app/components/stepper"}],"blocks":[{"name":"auth","title":"Auth","description":"Authentication pages (login, signup, forgot password)","tags":["auth","login","signup","authentication","form"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/card","@spartan-ng/helm/form-field"],"files":[{"id":"forgot-password","file":"forgot-password.component.ts","title":"Forgot Password","description":"Forgot Password"},{"id":"login","file":"login.component.ts","title":"Login","description":"Login"},{"id":"register","file":"register.component.ts","title":"Register","description":"Register"}],"path":"templates/ui/blocks/auth","defaultOutput":"src/app/blocks/auth"},{"name":"settings","title":"Settings","description":"Settings pages: profile, notifications, security, danger zone","tags":["settings","preferences","account","profile","security"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/card","@spartan-ng/helm/form-field","@spartan-ng/helm/switch"],"files":[{"id":"danger-zone","file":"danger-zone.component.ts","title":"Danger Zone","description":"Danger Zone"},{"id":"notification","file":"notification-settings.component.ts","title":"Notification Settings","description":"Notification Settings"},{"id":"profile","file":"profile-settings.component.ts","title":"Profile Settings","description":"Profile Settings"},{"id":"security","file":"security-settings.component.ts","title":"Security Settings","description":"Security Settings"}],"path":"templates/ui/blocks/settings","defaultOutput":"src/app/blocks/settings"},{"name":"shell","title":"Shell","description":"Application shell layouts: sidebar, topnav, collapsible - each with optional footer variant","tags":["shell","layout","sidebar","header","footer","navigation","topnav","collapsible"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/icon","@spartan-ng/helm/dropdown-menu"],"files":[{"id":"collapsible-footer","file":"collapsible-shell-footer.component.ts","title":"Collapsible Shell Footer","description":"Collapsible Shell Footer"},{"id":"collapsible","file":"collapsible-shell.component.ts","title":"Collapsible Shell","description":"Collapsible Shell"},{"id":"sidebar-footer","file":"sidebar-shell-footer.component.ts","title":"Sidebar Shell Footer","description":"Sidebar Shell Footer"},{"id":"sidebar","file":"sidebar-shell.component.ts","title":"Sidebar Shell","description":"Sidebar Shell"},{"id":"topnav-footer","file":"topnav-shell-footer.component.ts","title":"Topnav Shell Footer","description":"Topnav Shell Footer"},{"id":"topnav","file":"topnav-shell.component.ts","title":"Topnav Shell","description":"Topnav Shell"}],"path":"templates/ui/blocks/shell","defaultOutput":"src/app/blocks/shell"}]}}
@@ -124,7 +124,8 @@ async function fetchCatalog(options = {}) {
124
124
  blocks: catalog.blocks.map(b => ({
125
125
  ...b,
126
126
  path: `templates/ui/blocks/${b.name}`,
127
- defaultOutput: `src/app/blocks/${b.name}`
127
+ defaultOutput: `src/app/blocks/${b.name}`,
128
+ files: b.files || []
128
129
  }))
129
130
  };
130
131
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * CLI Metadata - Single source of truth for CLI commands
3
+ * Used by both CLI and MCP server
4
+ */
5
+
6
+ const CLI_METADATA = {
7
+ name: 'grg',
8
+ version: '0.6.2',
9
+ description: 'GRG Kit CLI - Initialize your Angular project with GRG Kit components and add blocks',
10
+
11
+ commands: {
12
+ init: {
13
+ name: 'init',
14
+ description: 'Initialize GRG Kit in current Angular project: installs Tailwind CSS v4, Spartan-NG, theme, and examples',
15
+ usage: 'grg init [options]',
16
+ options: [
17
+ {
18
+ flag: '-t, --theme <name>',
19
+ description: 'Theme to install',
20
+ default: 'grg-theme',
21
+ choices: ['grg-theme', 'claude', 'clean-slate', 'modern-minimal', 'amber-minimal', 'mocks']
22
+ }
23
+ ],
24
+ examples: [
25
+ { command: 'grg init', description: 'Initialize with default theme' },
26
+ { command: 'grg init --theme claude', description: 'Initialize with Claude theme' }
27
+ ]
28
+ },
29
+
30
+ addBlock: {
31
+ name: 'add block',
32
+ description: 'Add blocks to your project',
33
+ usage: 'grg add block <blockName> [fileIds...]',
34
+ options: [
35
+ { flag: '--all', description: 'Add all blocks' },
36
+ { flag: '-o, --output <path>', description: 'Custom output directory' }
37
+ ],
38
+ blocks: ['auth', 'shell', 'settings'],
39
+ examples: [
40
+ { command: 'grg add block auth', description: 'Add all auth files' },
41
+ { command: 'grg add block auth login', description: 'Add only login file' },
42
+ { command: 'grg add block auth login register', description: 'Add login and register files' },
43
+ { command: 'grg add block shell sidebar', description: 'Add sidebar shell layout' },
44
+ { command: 'grg add block --all', description: 'Add all blocks' }
45
+ ]
46
+ },
47
+
48
+ list: {
49
+ name: 'list',
50
+ description: 'List available blocks and components',
51
+ usage: 'grg list [category]',
52
+ categories: ['blocks', 'themes', 'components'],
53
+ examples: [
54
+ { command: 'grg list', description: 'List all resources' },
55
+ { command: 'grg list blocks', description: 'List available blocks' }
56
+ ]
57
+ }
58
+ },
59
+
60
+ // Helper to generate install command for a block
61
+ getBlockInstallCommand: (blockName, fileIds = []) => {
62
+ const files = fileIds.length > 0 ? ` ${fileIds.join(' ')}` : '';
63
+ return `grg add block ${blockName}${files}`;
64
+ },
65
+
66
+ // Helper to generate init command with theme
67
+ getInitCommand: (themeName) => {
68
+ return themeName ? `grg init --theme ${themeName}` : 'grg init';
69
+ }
70
+ };
71
+
72
+ module.exports = { CLI_METADATA };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grg-kit-cli",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "CLI tool for pulling GRG Kit resources into your Angular project",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -298,9 +298,32 @@ module.exports = { RESOURCES, REPO };
298
298
 
299
299
  const totalFiles = catalogBlocks.reduce((sum, b) => sum + (b.files?.length || 0), 0);
300
300
 
301
+ // Read CLI version from package.json
302
+ const cliPackage = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
303
+
301
304
  const catalog = {
302
- version: '1.0.0',
305
+ version: '1.0.1',
303
306
  lastUpdated: new Date().toISOString().split('T')[0],
307
+ cli: {
308
+ name: 'grg',
309
+ version: cliPackage.version,
310
+ commands: {
311
+ init: {
312
+ usage: 'grg init [--theme <name>]',
313
+ description: 'Initialize GRG Kit in current Angular project',
314
+ themeFlag: '--theme'
315
+ },
316
+ addBlock: {
317
+ usage: 'grg add block <blockName> [fileIds...]',
318
+ description: 'Add blocks to your project',
319
+ validBlocks: catalogBlocks.map(b => b.name)
320
+ },
321
+ list: {
322
+ usage: 'grg list [category]',
323
+ description: 'List available resources'
324
+ }
325
+ }
326
+ },
304
327
  themes: catalogThemes,
305
328
  components: catalogComponents,
306
329
  blocks: catalogBlocks