@gilav21/shadcn-angular 0.0.1

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.
@@ -0,0 +1,9 @@
1
+ interface AddOptions {
2
+ yes?: boolean;
3
+ overwrite?: boolean;
4
+ all?: boolean;
5
+ path?: string;
6
+ remote?: boolean;
7
+ }
8
+ export declare function add(components: string[], options: AddOptions): Promise<void>;
9
+ export {};
@@ -0,0 +1,161 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import prompts from 'prompts';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import { getConfig } from '../utils/config.js';
8
+ import { registry } from '../registry/index.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ // Base URL for the component registry (GitHub raw content)
12
+ const REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/main/packages/components/ui';
13
+ // Components source directory (relative to CLI dist folder) for local dev
14
+ function getLocalComponentsDir() {
15
+ // From dist/commands/add.js -> packages/components/ui
16
+ const fromDist = path.resolve(__dirname, '../../../components/ui');
17
+ if (fs.existsSync(fromDist)) {
18
+ return fromDist;
19
+ }
20
+ // Fallback: from src/commands/add.ts -> packages/components/ui
21
+ const fromSrc = path.resolve(__dirname, '../../../components/ui');
22
+ if (fs.existsSync(fromSrc)) {
23
+ return fromSrc;
24
+ }
25
+ return null;
26
+ }
27
+ async function fetchComponentContent(file, options) {
28
+ const localDir = getLocalComponentsDir();
29
+ // 1. Prefer local if available and not forced remote
30
+ if (localDir && !options.remote) {
31
+ const localPath = path.join(localDir, file);
32
+ if (await fs.pathExists(localPath)) {
33
+ return fs.readFile(localPath, 'utf-8');
34
+ }
35
+ }
36
+ // 2. Fetch from remote registry
37
+ const url = `${REGISTRY_BASE_URL}/${file}`;
38
+ try {
39
+ const response = await fetch(url);
40
+ if (!response.ok) {
41
+ throw new Error(`Failed to fetch component from ${url}: ${response.statusText}`);
42
+ }
43
+ return await response.text();
44
+ }
45
+ catch (error) {
46
+ if (localDir) {
47
+ throw new Error(`Component file not found locally or remotely: ${file}`);
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+ export async function add(components, options) {
53
+ const cwd = process.cwd();
54
+ // Load config
55
+ const config = await getConfig(cwd);
56
+ if (!config) {
57
+ console.log(chalk.red('Error: components.json not found.'));
58
+ console.log(chalk.dim('Run `npx shadcn-angular init` first.'));
59
+ process.exit(1);
60
+ }
61
+ // Get components to add
62
+ let componentsToAdd = [];
63
+ if (options.all) {
64
+ componentsToAdd = Object.keys(registry);
65
+ }
66
+ else if (components.length === 0) {
67
+ const { selected } = await prompts({
68
+ type: 'multiselect',
69
+ name: 'selected',
70
+ message: 'Which components would you like to add?',
71
+ choices: Object.keys(registry).map(name => ({
72
+ title: name,
73
+ value: name,
74
+ })),
75
+ hint: '- Space to select, Enter to confirm',
76
+ });
77
+ componentsToAdd = selected;
78
+ }
79
+ else {
80
+ componentsToAdd = components;
81
+ }
82
+ if (!componentsToAdd || componentsToAdd.length === 0) {
83
+ console.log(chalk.dim('No components selected.'));
84
+ return;
85
+ }
86
+ // Validate components exist
87
+ const invalidComponents = componentsToAdd.filter(c => !registry[c]);
88
+ if (invalidComponents.length > 0) {
89
+ console.log(chalk.red(`Invalid component(s): ${invalidComponents.join(', ')}`));
90
+ console.log(chalk.dim('Available components: ' + Object.keys(registry).join(', ')));
91
+ process.exit(1);
92
+ }
93
+ // Resolve dependencies
94
+ const allComponents = new Set();
95
+ const resolveDeps = (name) => {
96
+ if (allComponents.has(name))
97
+ return;
98
+ allComponents.add(name);
99
+ const component = registry[name];
100
+ if (component.dependencies) {
101
+ component.dependencies.forEach(dep => resolveDeps(dep));
102
+ }
103
+ };
104
+ componentsToAdd.forEach(c => resolveDeps(c));
105
+ const targetDir = options.path
106
+ ? path.join(cwd, options.path)
107
+ : path.join(cwd, 'src/components/ui');
108
+ // Check for existing files
109
+ const existing = [];
110
+ for (const name of allComponents) {
111
+ const component = registry[name];
112
+ for (const file of component.files) {
113
+ const targetPath = path.join(targetDir, file);
114
+ if (await fs.pathExists(targetPath)) {
115
+ existing.push(file);
116
+ }
117
+ }
118
+ }
119
+ if (existing.length > 0 && !options.overwrite && !options.yes) {
120
+ const { overwrite } = await prompts({
121
+ type: 'confirm',
122
+ name: 'overwrite',
123
+ message: `The following files already exist: ${existing.join(', ')}. Overwrite?`,
124
+ initial: false,
125
+ });
126
+ if (!overwrite) {
127
+ console.log(chalk.dim('Installation cancelled.'));
128
+ return;
129
+ }
130
+ }
131
+ const spinner = ora('Installing components...').start();
132
+ try {
133
+ await fs.ensureDir(targetDir);
134
+ for (const name of allComponents) {
135
+ const component = registry[name];
136
+ for (const file of component.files) {
137
+ const targetPath = path.join(targetDir, file);
138
+ try {
139
+ const content = await fetchComponentContent(file, options);
140
+ await fs.ensureDir(path.dirname(targetPath));
141
+ await fs.writeFile(targetPath, content);
142
+ spinner.text = `Added ${file}`;
143
+ }
144
+ catch (err) {
145
+ spinner.warn(`Could not add ${file}: ${err.message}`);
146
+ }
147
+ }
148
+ }
149
+ spinner.succeed(chalk.green(`Added ${allComponents.size} component(s)`));
150
+ console.log('\n' + chalk.dim('Components added:'));
151
+ allComponents.forEach(name => {
152
+ console.log(chalk.dim(' - ') + chalk.cyan(name));
153
+ });
154
+ console.log('');
155
+ }
156
+ catch (error) {
157
+ spinner.fail('Failed to add components');
158
+ console.error(error);
159
+ process.exit(1);
160
+ }
161
+ }
@@ -0,0 +1,6 @@
1
+ interface InitOptions {
2
+ yes?: boolean;
3
+ defaults?: boolean;
4
+ }
5
+ export declare function init(options: InitOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,135 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import prompts from 'prompts';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { execa } from 'execa';
7
+ import { getDefaultConfig } from '../utils/config.js';
8
+ import { getStylesTemplate } from '../templates/styles.js';
9
+ import { getUtilsTemplate } from '../templates/utils.js';
10
+ export async function init(options) {
11
+ console.log(chalk.bold('\n🎨 Welcome to shadcn-angular!\n'));
12
+ const cwd = process.cwd();
13
+ // Check if this is an Angular project
14
+ const angularJsonPath = path.join(cwd, 'angular.json');
15
+ if (!await fs.pathExists(angularJsonPath)) {
16
+ console.log(chalk.red('Error: This does not appear to be an Angular project.'));
17
+ console.log(chalk.dim('Please run this command in the root of your Angular project.'));
18
+ process.exit(1);
19
+ }
20
+ // Check if already initialized
21
+ const componentsJsonPath = path.join(cwd, 'components.json');
22
+ if (await fs.pathExists(componentsJsonPath)) {
23
+ const { overwrite } = await prompts({
24
+ type: 'confirm',
25
+ name: 'overwrite',
26
+ message: 'components.json already exists. Overwrite?',
27
+ initial: false,
28
+ });
29
+ if (!overwrite) {
30
+ console.log(chalk.dim('Initialization cancelled.'));
31
+ return;
32
+ }
33
+ }
34
+ let config;
35
+ if (options.defaults || options.yes) {
36
+ config = getDefaultConfig();
37
+ }
38
+ else {
39
+ const responses = await prompts([
40
+ {
41
+ type: 'select',
42
+ name: 'style',
43
+ message: 'Which style would you like to use?',
44
+ choices: [
45
+ { title: 'Default', value: 'default' },
46
+ { title: 'New York', value: 'new-york' },
47
+ ],
48
+ initial: 0,
49
+ },
50
+ {
51
+ type: 'select',
52
+ name: 'baseColor',
53
+ message: 'Which color would you like to use as base color?',
54
+ choices: [
55
+ { title: 'Neutral', value: 'neutral' },
56
+ { title: 'Slate', value: 'slate' },
57
+ { title: 'Stone', value: 'stone' },
58
+ { title: 'Gray', value: 'gray' },
59
+ { title: 'Zinc', value: 'zinc' },
60
+ ],
61
+ initial: 0,
62
+ },
63
+ {
64
+ type: 'text',
65
+ name: 'componentsPath',
66
+ message: 'Where would you like to install components?',
67
+ initial: 'src/components/ui',
68
+ },
69
+ {
70
+ type: 'text',
71
+ name: 'utilsPath',
72
+ message: 'Where would you like to install utils?',
73
+ initial: 'src/components/lib',
74
+ },
75
+ {
76
+ type: 'text',
77
+ name: 'globalCss',
78
+ message: 'Where is your global CSS file?',
79
+ initial: 'src/styles.scss',
80
+ },
81
+ ]);
82
+ config = {
83
+ $schema: 'https://shadcn-angular.dev/schema.json',
84
+ style: responses.style,
85
+ tailwind: {
86
+ css: responses.globalCss,
87
+ baseColor: responses.baseColor,
88
+ cssVariables: true,
89
+ },
90
+ aliases: {
91
+ components: '@/components',
92
+ utils: '@/lib/utils',
93
+ ui: '@/components/ui',
94
+ },
95
+ iconLibrary: 'lucide-angular',
96
+ };
97
+ }
98
+ const spinner = ora('Initializing project...').start();
99
+ try {
100
+ // Write components.json
101
+ await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
102
+ spinner.text = 'Created components.json';
103
+ // Create utils directory and file
104
+ const utilsDir = path.join(cwd, config.aliases.utils.replace('@/', 'src/').replace('/utils', ''));
105
+ await fs.ensureDir(utilsDir);
106
+ await fs.writeFile(path.join(utilsDir, 'utils.ts'), getUtilsTemplate());
107
+ spinner.text = 'Created utils.ts';
108
+ // Create/update styles file
109
+ const stylesPath = path.join(cwd, config.tailwind.css);
110
+ const existingStyles = await fs.pathExists(stylesPath)
111
+ ? await fs.readFile(stylesPath, 'utf-8')
112
+ : '';
113
+ if (!existingStyles.includes('--background:')) {
114
+ await fs.writeFile(stylesPath, getStylesTemplate(config.tailwind.baseColor) + '\n' + existingStyles);
115
+ spinner.text = 'Updated styles with theme variables';
116
+ }
117
+ // Create components/ui directory
118
+ const uiDir = path.join(cwd, 'src/components/ui');
119
+ await fs.ensureDir(uiDir);
120
+ spinner.text = 'Created components directory';
121
+ // Install dependencies
122
+ spinner.text = 'Installing dependencies...';
123
+ await execa('npm', ['install', 'clsx', 'tailwind-merge', 'class-variance-authority', '@angular/cdk', 'lucide-angular'], { cwd });
124
+ spinner.succeed(chalk.green('Project initialized successfully!'));
125
+ console.log('\n' + chalk.bold('Next steps:'));
126
+ console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx shadcn-angular add button'));
127
+ console.log(chalk.dim(' 2. Import and use in your templates'));
128
+ console.log('');
129
+ }
130
+ catch (error) {
131
+ spinner.fail('Failed to initialize project');
132
+ console.error(error);
133
+ process.exit(1);
134
+ }
135
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { init } from './commands/init.js';
4
+ import { add } from './commands/add.js';
5
+ const program = new Command();
6
+ program
7
+ .name('shadcn-angular')
8
+ .description('CLI for adding shadcn-angular components to your Angular project')
9
+ .version('0.0.1');
10
+ program
11
+ .command('init')
12
+ .description('Initialize shadcn-angular in your project')
13
+ .option('-y, --yes', 'Skip confirmation prompt')
14
+ .option('-d, --defaults', 'Use default configuration')
15
+ .action(init);
16
+ program
17
+ .command('add')
18
+ .description('Add a component to your project')
19
+ .argument('[components...]', 'The components to add')
20
+ .option('-y, --yes', 'Skip confirmation prompt')
21
+ .option('-o, --overwrite', 'Overwrite existing files')
22
+ .option('-a, --all', 'Add all available components')
23
+ .option('-p, --path <path>', 'The path to add the component to')
24
+ .option('--remote', 'Force remote fetch from GitHub registry')
25
+ .action(add);
26
+ program.parse();
@@ -0,0 +1,7 @@
1
+ export interface ComponentDefinition {
2
+ name: string;
3
+ files: string[];
4
+ dependencies?: string[];
5
+ }
6
+ export type ComponentName = keyof typeof registry;
7
+ export declare const registry: Record<string, ComponentDefinition>;
@@ -0,0 +1,224 @@
1
+ // Component Registry - Defines available components and their file mappings
2
+ // Actual component files are stored in packages/components/ui/
3
+ // Registry maps component names to their file definitions
4
+ // Files are relative to the components/ui directory
5
+ export const registry = {
6
+ accordion: {
7
+ name: 'accordion',
8
+ files: ['accordion.component.ts'],
9
+ },
10
+ alert: {
11
+ name: 'alert',
12
+ files: ['alert.component.ts'],
13
+ },
14
+ 'alert-dialog': {
15
+ name: 'alert-dialog',
16
+ files: ['alert-dialog.component.ts'],
17
+ dependencies: ['button'],
18
+ },
19
+ 'aspect-ratio': {
20
+ name: 'aspect-ratio',
21
+ files: ['aspect-ratio.component.ts'],
22
+ },
23
+ avatar: {
24
+ name: 'avatar',
25
+ files: ['avatar.component.ts'],
26
+ },
27
+ badge: {
28
+ name: 'badge',
29
+ files: ['badge.component.ts'],
30
+ },
31
+ breadcrumb: {
32
+ name: 'breadcrumb',
33
+ files: ['breadcrumb.component.ts'],
34
+ },
35
+ button: {
36
+ name: 'button',
37
+ files: ['button.component.ts'],
38
+ },
39
+ 'button-group': {
40
+ name: 'button-group',
41
+ files: ['button-group.component.ts'],
42
+ dependencies: ['button', 'separator'],
43
+ },
44
+ calendar: {
45
+ name: 'calendar',
46
+ files: ['calendar.component.ts'],
47
+ dependencies: ['button'],
48
+ },
49
+ card: {
50
+ name: 'card',
51
+ files: ['card.component.ts'],
52
+ },
53
+ carousel: {
54
+ name: 'carousel',
55
+ files: ['carousel.component.ts'],
56
+ dependencies: ['button'],
57
+ },
58
+ checkbox: {
59
+ name: 'checkbox',
60
+ files: ['checkbox.component.ts'],
61
+ },
62
+ collapsible: {
63
+ name: 'collapsible',
64
+ files: ['collapsible.component.ts'],
65
+ },
66
+ command: {
67
+ name: 'command',
68
+ files: ['command.component.ts'],
69
+ dependencies: ['dialog', 'separator'],
70
+ },
71
+ 'context-menu': {
72
+ name: 'context-menu',
73
+ files: ['context-menu.component.ts'],
74
+ },
75
+ 'date-picker': {
76
+ name: 'date-picker',
77
+ files: ['date-picker.component.ts'],
78
+ dependencies: ['calendar', 'popover', 'button'],
79
+ },
80
+ dialog: {
81
+ name: 'dialog',
82
+ files: ['dialog.component.ts'],
83
+ },
84
+ drawer: {
85
+ name: 'drawer',
86
+ files: ['drawer.component.ts'],
87
+ },
88
+ 'dropdown-menu': {
89
+ name: 'dropdown-menu',
90
+ files: ['dropdown-menu.component.ts'],
91
+ },
92
+ empty: {
93
+ name: 'empty',
94
+ files: ['empty.component.ts'],
95
+ },
96
+ field: {
97
+ name: 'field',
98
+ files: ['field.component.ts'],
99
+ },
100
+ 'hover-card': {
101
+ name: 'hover-card',
102
+ files: ['hover-card.component.ts'],
103
+ },
104
+ input: {
105
+ name: 'input',
106
+ files: ['input.component.ts'],
107
+ },
108
+ 'input-group': {
109
+ name: 'input-group',
110
+ files: ['input-group.component.ts'],
111
+ dependencies: ['input', 'button'],
112
+ },
113
+ 'input-otp': {
114
+ name: 'input-otp',
115
+ files: ['input-otp.component.ts'],
116
+ },
117
+ kbd: {
118
+ name: 'kbd',
119
+ files: ['kbd.component.ts'],
120
+ },
121
+ label: {
122
+ name: 'label',
123
+ files: ['label.component.ts'],
124
+ },
125
+ menubar: {
126
+ name: 'menubar',
127
+ files: ['menubar.component.ts'],
128
+ },
129
+ 'native-select': {
130
+ name: 'native-select',
131
+ files: ['native-select.component.ts'],
132
+ },
133
+ 'navigation-menu': {
134
+ name: 'navigation-menu',
135
+ files: ['navigation-menu.component.ts'],
136
+ },
137
+ pagination: {
138
+ name: 'pagination',
139
+ files: ['pagination.component.ts'],
140
+ dependencies: ['button'],
141
+ },
142
+ popover: {
143
+ name: 'popover',
144
+ files: ['popover.component.ts'],
145
+ },
146
+ progress: {
147
+ name: 'progress',
148
+ files: ['progress.component.ts'],
149
+ },
150
+ 'radio-group': {
151
+ name: 'radio-group',
152
+ files: ['radio-group.component.ts'],
153
+ },
154
+ resizable: {
155
+ name: 'resizable',
156
+ files: ['resizable.component.ts'],
157
+ },
158
+ 'scroll-area': {
159
+ name: 'scroll-area',
160
+ files: ['scroll-area.component.ts'],
161
+ },
162
+ select: {
163
+ name: 'select',
164
+ files: ['select.component.ts'],
165
+ },
166
+ separator: {
167
+ name: 'separator',
168
+ files: ['separator.component.ts'],
169
+ },
170
+ sheet: {
171
+ name: 'sheet',
172
+ files: ['sheet.component.ts'],
173
+ },
174
+ sidebar: {
175
+ name: 'sidebar',
176
+ files: ['sidebar.component.ts'],
177
+ dependencies: ['button', 'sheet', 'separator', 'tooltip', 'input', 'skeleton'],
178
+ },
179
+ skeleton: {
180
+ name: 'skeleton',
181
+ files: ['skeleton.component.ts'],
182
+ },
183
+ slider: {
184
+ name: 'slider',
185
+ files: ['slider.component.ts'],
186
+ },
187
+ spinner: {
188
+ name: 'spinner',
189
+ files: ['spinner.component.ts'],
190
+ },
191
+ switch: {
192
+ name: 'switch',
193
+ files: ['switch.component.ts'],
194
+ },
195
+ table: {
196
+ name: 'table',
197
+ files: ['table.component.ts'],
198
+ },
199
+ tabs: {
200
+ name: 'tabs',
201
+ files: ['tabs.component.ts'],
202
+ },
203
+ textarea: {
204
+ name: 'textarea',
205
+ files: ['textarea.component.ts'],
206
+ },
207
+ toast: {
208
+ name: 'toast',
209
+ files: ['toast.component.ts'],
210
+ },
211
+ toggle: {
212
+ name: 'toggle',
213
+ files: ['toggle.component.ts'],
214
+ },
215
+ 'toggle-group': {
216
+ name: 'toggle-group',
217
+ files: ['toggle-group.component.ts'],
218
+ dependencies: ['toggle'],
219
+ },
220
+ tooltip: {
221
+ name: 'tooltip',
222
+ files: ['tooltip.component.ts'],
223
+ },
224
+ };
@@ -0,0 +1,3 @@
1
+ type BaseColor = 'neutral' | 'slate' | 'stone' | 'gray' | 'zinc';
2
+ export declare function getStylesTemplate(baseColor?: BaseColor): string;
3
+ export {};