@gilav21/shadcn-angular 0.0.24 → 0.0.26

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,99 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { getConfig } from '../utils/config.js';
5
+ import { registry, getComponentNames } from '../registry/index.js';
6
+ import { resolveProjectPath, aliasToProjectPath } from '../utils/paths.js';
7
+ import { fetchAndTransform, normalizeContent } from './add.js';
8
+ function formatLineDiff(localLine, remoteLine) {
9
+ if (localLine !== undefined && remoteLine !== undefined) {
10
+ return [chalk.red(`- ${localLine}`), chalk.green(`+ ${remoteLine}`)];
11
+ }
12
+ if (localLine === undefined) {
13
+ return [chalk.green(`+ ${remoteLine}`)];
14
+ }
15
+ return [chalk.red(`- ${localLine}`)];
16
+ }
17
+ export function formatUnifiedDiff(fileName, localContent, remoteContent) {
18
+ const localLines = localContent.split('\n');
19
+ const remoteLines = remoteContent.split('\n');
20
+ const output = [
21
+ chalk.bold(`--- local/${fileName}`),
22
+ chalk.bold(`+++ remote/${fileName}`),
23
+ ];
24
+ const maxLines = Math.max(localLines.length, remoteLines.length);
25
+ let diffFound = false;
26
+ for (let i = 0; i < maxLines; i++) {
27
+ const localLine = localLines[i];
28
+ const remoteLine = remoteLines[i];
29
+ if (localLine === remoteLine)
30
+ continue;
31
+ diffFound = true;
32
+ output.push(chalk.cyan(`@@ line ${i + 1} @@`), ...formatLineDiff(localLine, remoteLine));
33
+ }
34
+ return diffFound ? output.join('\n') : '';
35
+ }
36
+ async function diffFile(file, targetDir, options, utilsAlias) {
37
+ const targetPath = path.join(targetDir, file);
38
+ if (!await fs.pathExists(targetPath))
39
+ return null;
40
+ try {
41
+ const localContent = normalizeContent(await fs.readFile(targetPath, 'utf-8'));
42
+ const remoteContent = normalizeContent(await fetchAndTransform(file, { branch: options.branch, remote: options.remote, registry: options.registry }, utilsAlias));
43
+ if (localContent === remoteContent)
44
+ return null;
45
+ const diffOutput = formatUnifiedDiff(file, localContent, remoteContent);
46
+ return diffOutput || null;
47
+ }
48
+ catch {
49
+ return chalk.yellow(` Could not fetch remote version of ${file}`);
50
+ }
51
+ }
52
+ async function diffComponent(name, targetDir, options, utilsAlias) {
53
+ const component = registry[name];
54
+ const fileDiffs = [];
55
+ for (const file of component.files) {
56
+ const result = await diffFile(file, targetDir, options, utilsAlias);
57
+ if (result)
58
+ fileDiffs.push(result);
59
+ }
60
+ return fileDiffs;
61
+ }
62
+ export async function diff(components, options) {
63
+ const cwd = process.cwd();
64
+ const config = await getConfig(cwd);
65
+ if (!config) {
66
+ console.log(chalk.red('Error: components.json not found.'));
67
+ console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
68
+ process.exit(1);
69
+ }
70
+ if (!options.registry && config.registry) {
71
+ options.registry = config.registry;
72
+ }
73
+ const uiBasePath = aliasToProjectPath(config.aliases.ui || 'src/components/ui');
74
+ const targetDir = resolveProjectPath(cwd, uiBasePath);
75
+ const utilsAlias = config.aliases.utils;
76
+ const names = components.length > 0
77
+ ? components
78
+ : getComponentNames();
79
+ let totalDiffs = 0;
80
+ for (const name of names) {
81
+ if (!(name in registry)) {
82
+ console.log(chalk.red(`Unknown component: ${name}`));
83
+ continue;
84
+ }
85
+ const fileDiffs = await diffComponent(name, targetDir, options, utilsAlias);
86
+ if (fileDiffs.length > 0) {
87
+ totalDiffs++;
88
+ console.log(chalk.bold.cyan(`\n${name}:`));
89
+ for (const d of fileDiffs)
90
+ console.log(d);
91
+ }
92
+ }
93
+ if (totalDiffs === 0) {
94
+ console.log(chalk.green('\nAll installed components are up to date.'));
95
+ }
96
+ else {
97
+ console.log(chalk.dim(`\n${totalDiffs} component(s) have differences.`));
98
+ }
99
+ }
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { registry } from '../registry/index.js';
2
+ import { registry, getComponentNames } from '../registry/index.js';
3
3
  const CATEGORY_ORDER = ['UI', 'Charts', 'Layout / Page Building', 'Animation', 'Kanban'];
4
4
  const ANIMATION_COMPONENTS = new Set([
5
5
  'gradient-text', 'flip-text', 'meteors', 'shine-border', 'scroll-progress',
@@ -10,9 +10,9 @@ const ANIMATION_COMPONENTS = new Set([
10
10
  const KANBAN_COMPONENTS = new Set(['kanban']);
11
11
  const LAYOUT_COMPONENTS = new Set(['bento-grid', 'page-builder']);
12
12
  function categorize(name) {
13
- const def = registry[name];
14
- if (!def)
13
+ if (!(name in registry))
15
14
  return 'UI';
15
+ const def = registry[name];
16
16
  const hasChartFile = def.files.some(f => f.startsWith('charts/'));
17
17
  if (hasChartFile)
18
18
  return 'Charts';
@@ -29,14 +29,14 @@ function buildComponentsByCategory() {
29
29
  for (const cat of CATEGORY_ORDER) {
30
30
  groups.set(cat, []);
31
31
  }
32
- for (const name of Object.keys(registry)) {
32
+ for (const name of getComponentNames()) {
33
33
  const cat = categorize(name);
34
34
  const list = groups.get(cat);
35
35
  if (list)
36
36
  list.push(name);
37
37
  }
38
38
  for (const list of groups.values()) {
39
- list.sort();
39
+ list.sort((a, b) => a.localeCompare(b));
40
40
  }
41
41
  return groups;
42
42
  }
@@ -57,17 +57,26 @@ function buildCommandsSection() {
57
57
  ' ' + chalk.cyan('init') + ' Initialize shadcn-angular in your project',
58
58
  ' ' + chalk.gray('-y, --yes') + ' Skip confirmation prompt',
59
59
  ' ' + chalk.gray('-d, --defaults') + ' Use default configuration',
60
+ ' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
60
61
  ' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
61
62
  '',
62
63
  ' ' + chalk.cyan('add') + ' Add component(s) to your project',
63
64
  ' ' + chalk.gray('[components...]') + ' One or more component names',
64
- ' ' + chalk.gray('-y, --yes') + ' Skip confirmation prompt',
65
+ ' ' + chalk.gray('-y, --yes') + ' Skip prompts and overwrite conflicts',
65
66
  ' ' + chalk.gray('-o, --overwrite') + ' Overwrite existing files',
66
67
  ' ' + chalk.gray('-a, --all') + ' Add all available components',
67
68
  ' ' + chalk.gray('-p, --path') + ' <path> Custom install path',
68
69
  ' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
70
+ ' ' + chalk.gray('--dry-run') + ' Show what would be installed without changes',
69
71
  ' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
70
72
  '',
73
+ ' ' + chalk.cyan('diff') + ' Show differences between local and remote versions',
74
+ ' ' + chalk.gray('[components...]') + ' Components to diff (all installed if omitted)',
75
+ ' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
76
+ ' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
77
+ '',
78
+ ' ' + chalk.cyan('list') + ' List all components and their install status',
79
+ '',
71
80
  ' ' + chalk.cyan('help') + ' Show this reference',
72
81
  '',
73
82
  ];
@@ -1,7 +1,9 @@
1
1
  interface InitOptions {
2
2
  yes?: boolean;
3
3
  defaults?: boolean;
4
+ remote?: boolean;
4
5
  branch: string;
6
+ registry?: string;
5
7
  }
6
8
  export declare function init(options: InitOptions): Promise<void>;
7
9
  export {};
@@ -1,6 +1,5 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
3
  import prompts from 'prompts';
5
4
  import chalk from 'chalk';
6
5
  import ora from 'ora';
@@ -9,58 +8,187 @@ import { getStylesTemplate } from '../templates/styles.js';
9
8
  import { getUtilsTemplate } from '../templates/utils.js';
10
9
  import { installPackages } from '../utils/package-manager.js';
11
10
  import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
12
- function getLibRegistryBaseUrl(branch) {
13
- return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/lib`;
14
- }
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = path.dirname(__filename);
17
- function getLocalLibDir() {
18
- const fromDist = path.resolve(__dirname, '../../../components/lib');
19
- if (fs.existsSync(fromDist)) {
20
- return fromDist;
21
- }
22
- return null;
23
- }
24
- async function fetchLibFileContent(file, branch) {
11
+ import { getLibRegistryBaseUrl, getLocalLibDir, resolveProjectPath, aliasToProjectPath, } from '../utils/paths.js';
12
+ const onCancel = () => {
13
+ console.log(chalk.dim('\nCancelled.'));
14
+ process.exit(0);
15
+ };
16
+ async function fetchLibFileContent(file, branch, remote, registry) {
25
17
  const localLibDir = getLocalLibDir();
26
- if (localLibDir) {
18
+ if (localLibDir && !remote) {
27
19
  const localPath = path.join(localLibDir, file);
28
20
  if (await fs.pathExists(localPath)) {
29
21
  return fs.readFile(localPath, 'utf-8');
30
22
  }
31
23
  }
32
- const url = `${getLibRegistryBaseUrl(branch)}/${file}`;
24
+ const url = `${getLibRegistryBaseUrl(branch, registry)}/${file}`;
33
25
  const response = await fetch(url);
34
26
  if (!response.ok) {
35
27
  throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
36
28
  }
37
29
  return response.text();
38
30
  }
39
- function resolveProjectPath(cwd, inputPath) {
40
- const resolved = path.resolve(cwd, inputPath);
41
- const relative = path.relative(cwd, resolved);
42
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
43
- throw new Error(`Path must stay inside the project directory: ${inputPath}`);
31
+ function resolveAliasOrPath(cwd, aliasOrPath) {
32
+ return resolveProjectPath(cwd, aliasToProjectPath(aliasOrPath));
33
+ }
34
+ function toAlias(inputPath) {
35
+ return inputPath.startsWith('src/')
36
+ ? '@/' + inputPath.slice(4)
37
+ : inputPath;
38
+ }
39
+ async function promptForConfig() {
40
+ const THEME_COLORS = {
41
+ zinc: '#71717a', slate: '#64748b', stone: '#78716c',
42
+ gray: '#6b7280', neutral: '#737373', red: '#ef4444',
43
+ rose: '#f43f5e', orange: '#f97316', green: '#22c55e',
44
+ blue: '#3b82f6', yellow: '#eab308', violet: '#8b5cf6',
45
+ amber: '#d97706',
46
+ };
47
+ const colorSwatch = (value, label) => `${chalk.hex(THEME_COLORS[value])('██')} ${label}`;
48
+ const themeChoices = [
49
+ 'Zinc', 'Slate', 'Stone', 'Gray', 'Neutral',
50
+ 'Red', 'Rose', 'Orange', 'Green', 'Blue',
51
+ 'Yellow', 'Violet', 'Amber',
52
+ ].map(label => {
53
+ const value = label.toLowerCase();
54
+ return { title: colorSwatch(value, label), value };
55
+ });
56
+ const baseColorChoices = ['Neutral', 'Slate', 'Stone', 'Gray', 'Zinc'].map(label => {
57
+ const value = label.toLowerCase();
58
+ return { title: colorSwatch(value, label), value };
59
+ });
60
+ const responses = await prompts([
61
+ {
62
+ type: 'select',
63
+ name: 'baseColor',
64
+ message: 'Which color would you like to use as base color?',
65
+ choices: baseColorChoices,
66
+ initial: 0,
67
+ },
68
+ {
69
+ type: 'select',
70
+ name: 'theme',
71
+ message: 'Which color would you like to use for the main theme?',
72
+ choices: themeChoices,
73
+ initial: (prev) => {
74
+ const index = themeChoices.findIndex(c => c.value === prev);
75
+ return index === -1 ? 0 : index;
76
+ },
77
+ },
78
+ {
79
+ type: 'text',
80
+ name: 'componentsPath',
81
+ message: 'Where would you like to install components?',
82
+ initial: 'src/components/ui',
83
+ },
84
+ {
85
+ type: 'text',
86
+ name: 'utilsPath',
87
+ message: 'Where would you like to install utils?',
88
+ initial: 'src/components/lib',
89
+ },
90
+ {
91
+ type: 'text',
92
+ name: 'globalCss',
93
+ message: 'Where is your global styles file?',
94
+ initial: 'src/styles.scss',
95
+ },
96
+ {
97
+ type: 'confirm',
98
+ name: 'createShortcutRegistry',
99
+ message: 'Would you like to create a shortcut registry scaffold?',
100
+ initial: true,
101
+ },
102
+ ], { onCancel });
103
+ const componentsAlias = toAlias(responses.componentsPath);
104
+ const uiAlias = componentsAlias.endsWith('/ui')
105
+ ? componentsAlias
106
+ : componentsAlias + '/ui';
107
+ return {
108
+ config: {
109
+ style: 'default',
110
+ tailwind: {
111
+ css: responses.globalCss,
112
+ baseColor: responses.baseColor,
113
+ theme: responses.theme,
114
+ cssVariables: true,
115
+ },
116
+ aliases: {
117
+ components: componentsAlias.replace(/\/ui$/, ''),
118
+ utils: toAlias(responses.utilsPath),
119
+ ui: uiAlias,
120
+ },
121
+ },
122
+ createShortcutRegistry: responses.createShortcutRegistry ?? true,
123
+ };
124
+ }
125
+ async function installMissingDeps(cwd) {
126
+ const allDependencies = [
127
+ 'clsx', 'tailwind-merge', 'class-variance-authority',
128
+ 'tailwindcss', 'postcss', '@tailwindcss/postcss',
129
+ ];
130
+ const packageJsonPath = path.join(cwd, 'package.json');
131
+ let missingDeps = allDependencies;
132
+ if (await fs.pathExists(packageJsonPath)) {
133
+ const packageJson = await fs.readJson(packageJsonPath);
134
+ const installed = { ...packageJson.dependencies, ...packageJson.devDependencies };
135
+ missingDeps = allDependencies.filter(dep => !installed[dep]);
136
+ }
137
+ if (missingDeps.length > 0) {
138
+ await installPackages(missingDeps, { cwd });
44
139
  }
45
- return resolved;
46
140
  }
47
- function resolveAliasOrPath(cwd, aliasOrPath) {
48
- const normalized = aliasOrPath.startsWith('@/')
49
- ? path.join('src', aliasOrPath.slice(2))
50
- : aliasOrPath;
51
- return resolveProjectPath(cwd, normalized);
141
+ async function setupPostcss(cwd) {
142
+ const postcssConfigFiles = [
143
+ '.postcssrc.json', '.postcssrc.js', '.postcssrc.yaml',
144
+ 'postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs',
145
+ ];
146
+ const existing = (await Promise.all(postcssConfigFiles.map(async (f) => await fs.pathExists(path.join(cwd, f)) ? f : null))).filter(Boolean);
147
+ if (existing.length === 0) {
148
+ await fs.writeJson(path.join(cwd, '.postcssrc.json'), {
149
+ plugins: { '@tailwindcss/postcss': {} },
150
+ }, { spaces: 4 });
151
+ return;
152
+ }
153
+ if (!existing.includes('.postcssrc.json')) {
154
+ console.log(chalk.yellow(` Existing PostCSS config found (${existing[0]}). Skipping .postcssrc.json creation.`));
155
+ console.log(chalk.dim(' Make sure @tailwindcss/postcss is configured in your PostCSS config.'));
156
+ }
157
+ }
158
+ async function autoConfigureTsconfig(cwd, spinner) {
159
+ const tsconfigPath = path.join(cwd, 'tsconfig.json');
160
+ if (!await fs.pathExists(tsconfigPath))
161
+ return;
162
+ try {
163
+ const raw = await fs.readFile(tsconfigPath, 'utf-8');
164
+ const stripped = raw
165
+ .replaceAll(/\/\/.*$/gm, '')
166
+ .replaceAll(/\/\*[\s\S]*?\*\//g, '');
167
+ const tsconfig = JSON.parse(stripped);
168
+ const compilerOptions = tsconfig.compilerOptions ??= {};
169
+ const paths = compilerOptions.paths ??= {};
170
+ if (paths['@/*'])
171
+ return;
172
+ if (!compilerOptions.baseUrl) {
173
+ compilerOptions.baseUrl = '.';
174
+ }
175
+ paths['@/*'] = ['./src/*'];
176
+ await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
177
+ spinner.text = 'Configured tsconfig.json paths';
178
+ }
179
+ catch {
180
+ console.log(chalk.dim(' Could not auto-configure tsconfig.json — please add "@/*": ["./src/*"] to paths manually.'));
181
+ }
52
182
  }
53
183
  export async function init(options) {
54
184
  console.log(chalk.bold('\n🎨 Welcome to shadcn-angular!\n'));
55
185
  const cwd = process.cwd();
56
- // Check if this is an Angular project
57
186
  const angularJsonPath = path.join(cwd, 'angular.json');
58
187
  if (!await fs.pathExists(angularJsonPath)) {
59
188
  console.log(chalk.red('Error: This does not appear to be an Angular project.'));
60
189
  console.log(chalk.dim('Please run this command in the root of your Angular project.'));
61
190
  process.exit(1);
62
191
  }
63
- // Check if already initialized
64
192
  const componentsJsonPath = path.join(cwd, 'components.json');
65
193
  if (await fs.pathExists(componentsJsonPath)) {
66
194
  const overwrite = options.yes
@@ -70,200 +198,58 @@ export async function init(options) {
70
198
  name: 'overwrite',
71
199
  message: 'components.json already exists. Overwrite?',
72
200
  initial: false,
73
- })).overwrite;
201
+ }, { onCancel })).overwrite;
74
202
  if (!overwrite) {
75
203
  console.log(chalk.dim('Initialization cancelled.'));
76
204
  return;
77
205
  }
78
206
  }
79
- let config;
80
- let createShortcutRegistry = true;
81
- if (options.defaults || options.yes) {
82
- config = getDefaultConfig();
83
- }
84
- else {
85
- const THEME_COLORS = {
86
- zinc: '#71717a',
87
- slate: '#64748b',
88
- stone: '#78716c',
89
- gray: '#6b7280',
90
- neutral: '#737373',
91
- red: '#ef4444',
92
- rose: '#f43f5e',
93
- orange: '#f97316', // bright orange
94
- green: '#22c55e',
95
- blue: '#3b82f6',
96
- yellow: '#eab308',
97
- violet: '#8b5cf6',
98
- amber: '#d97706', // warm amber for preview
99
- };
100
- const themeChoices = [
101
- { title: 'Zinc', value: 'zinc' },
102
- { title: 'Slate', value: 'slate' },
103
- { title: 'Stone', value: 'stone' },
104
- { title: 'Gray', value: 'gray' },
105
- { title: 'Neutral', value: 'neutral' },
106
- { title: 'Red', value: 'red' },
107
- { title: 'Rose', value: 'rose' },
108
- { title: 'Orange', value: 'orange' },
109
- { title: 'Green', value: 'green' },
110
- { title: 'Blue', value: 'blue' },
111
- { title: 'Yellow', value: 'yellow' },
112
- { title: 'Violet', value: 'violet' },
113
- { title: 'Amber', value: 'amber' },
114
- ].map(c => ({
115
- ...c,
116
- title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
117
- }));
118
- const baseColorChoices = [
119
- { title: 'Neutral', value: 'neutral' },
120
- { title: 'Slate', value: 'slate' },
121
- { title: 'Stone', value: 'stone' },
122
- { title: 'Gray', value: 'gray' },
123
- { title: 'Zinc', value: 'zinc' },
124
- ].map(c => ({
125
- ...c,
126
- title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
127
- }));
128
- const responses = await prompts([
129
- {
130
- type: 'select',
131
- name: 'baseColor',
132
- message: 'Which color would you like to use as base color?',
133
- choices: baseColorChoices,
134
- initial: 0,
135
- },
136
- {
137
- type: 'select',
138
- name: 'theme',
139
- message: 'Which color would you like to use for the main theme?',
140
- choices: themeChoices,
141
- initial: (prev) => {
142
- const index = themeChoices.findIndex(c => c.value === prev);
143
- return index === -1 ? 0 : index;
144
- },
145
- },
146
- {
147
- type: 'text',
148
- name: 'componentsPath',
149
- message: 'Where would you like to install components?',
150
- initial: 'src/components/ui',
151
- },
152
- {
153
- type: 'text',
154
- name: 'utilsPath',
155
- message: 'Where would you like to install utils?',
156
- initial: 'src/components/lib',
157
- },
158
- {
159
- type: 'text',
160
- name: 'globalCss',
161
- message: 'Where is your global styles file?',
162
- initial: 'src/styles.scss',
163
- },
164
- {
165
- type: 'confirm',
166
- name: 'createShortcutRegistry',
167
- message: 'Would you like to create a shortcut registry scaffold?',
168
- initial: true,
169
- },
170
- ]);
171
- config = {
172
- $schema: 'https://shadcn-angular.dev/schema.json',
173
- style: 'default',
174
- tailwind: {
175
- css: responses.globalCss,
176
- baseColor: responses.baseColor,
177
- theme: responses.theme,
178
- cssVariables: true,
179
- },
180
- aliases: {
181
- components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
182
- utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
183
- ui: responses.componentsPath.replace('src/', '@/'),
184
- },
185
- };
186
- createShortcutRegistry = responses.createShortcutRegistry ?? true;
207
+ const { config, createShortcutRegistry } = options.defaults || options.yes
208
+ ? { config: getDefaultConfig(), createShortcutRegistry: true }
209
+ : await promptForConfig();
210
+ if (options.registry) {
211
+ config.registry = options.registry;
187
212
  }
188
213
  const spinner = ora('Initializing project...').start();
189
214
  try {
190
- // Write components.json
191
215
  await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
192
216
  spinner.text = 'Created components.json';
193
- // Create utils directory and file
194
- // Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
195
- // But we have the 'responses' object from CLI prompt only in the else block above!
196
- // So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
197
- // If config came from defaults, aliases are set.
198
- // We can reverse-map alias to path: @/ -> src/
199
217
  const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
200
218
  await fs.ensureDir(libDir);
201
219
  await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
202
220
  spinner.text = 'Created utils.ts';
203
- const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
204
- const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts', options.branch);
205
- await fs.writeFile(shortcutServicePath, shortcutServiceContent);
206
- spinner.text = 'Created shortcut-binding.service.ts';
207
221
  if (createShortcutRegistry) {
222
+ const content = await fetchLibFileContent('shortcut-binding.service.ts', options.branch, options.remote, options.registry);
223
+ await fs.writeFile(path.join(libDir, 'shortcut-binding.service.ts'), content);
224
+ spinner.text = 'Created shortcut-binding.service.ts';
208
225
  await writeShortcutRegistryIndex(cwd, config, []);
209
226
  spinner.text = 'Created shortcut-registry.index.ts';
210
227
  }
211
- // Create tailwind.css file in the same directory as the global styles
212
228
  const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
213
229
  const stylesDir = path.dirname(userStylesPath);
214
- const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
215
- // Write the tailwind.css file with all Tailwind directives
216
230
  await fs.ensureDir(stylesDir);
217
- await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
231
+ await fs.writeFile(path.join(stylesDir, 'tailwind.css'), getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
218
232
  spinner.text = 'Created tailwind.css';
219
- // Add import to the user's global styles file if not already present
220
233
  let userStyles = await fs.pathExists(userStylesPath)
221
234
  ? await fs.readFile(userStylesPath, 'utf-8')
222
235
  : '';
223
- const tailwindImport = '@import "./tailwind.css";';
224
236
  if (!userStyles.includes('tailwind.css')) {
225
- // Add import at the top of the file
226
- userStyles = tailwindImport + '\n\n' + userStyles;
237
+ userStyles = '@import "./tailwind.css";\n\n' + userStyles;
227
238
  await fs.writeFile(userStylesPath, userStyles);
228
239
  spinner.text = 'Added tailwind.css import to styles';
229
240
  }
230
- // Create components/ui directory
231
241
  const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
232
242
  await fs.ensureDir(uiDir);
233
243
  spinner.text = 'Created components directory';
234
- // Install dependencies
235
244
  spinner.text = 'Installing dependencies...';
236
- const dependencies = [
237
- 'clsx',
238
- 'tailwind-merge',
239
- 'class-variance-authority',
240
- 'tailwindcss',
241
- 'postcss',
242
- '@tailwindcss/postcss'
243
- ];
244
- await installPackages(dependencies, { cwd });
245
- // Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
245
+ await installMissingDeps(cwd);
246
246
  spinner.text = 'Configuring PostCSS...';
247
- const postcssrcPath = path.join(cwd, '.postcssrc.json');
248
- if (!await fs.pathExists(postcssrcPath)) {
249
- const configContent = {
250
- plugins: {
251
- '@tailwindcss/postcss': {}
252
- }
253
- };
254
- await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
255
- }
247
+ await setupPostcss(cwd);
248
+ await autoConfigureTsconfig(cwd, spinner);
256
249
  spinner.succeed(chalk.green('Project initialized successfully!'));
257
250
  console.log('\n' + chalk.bold('Next steps:'));
258
251
  console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
259
252
  console.log(chalk.dim(' 2. Import and use in your templates'));
260
- console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
261
- console.log(chalk.dim(' "compilerOptions": {'));
262
- console.log(chalk.dim(' "baseUrl": ".",'));
263
- console.log(chalk.dim(' "paths": {'));
264
- console.log(chalk.dim(' "@/*": ["./src/*"]'));
265
- console.log(chalk.dim(' }'));
266
- console.log(chalk.dim(' }'));
267
253
  console.log('');
268
254
  }
269
255
  catch (error) {
@@ -0,0 +1 @@
1
+ export declare function list(): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { getConfig } from '../utils/config.js';
5
+ import { registry, getComponentNames } from '../registry/index.js';
6
+ import { resolveProjectPath, aliasToProjectPath } from '../utils/paths.js';
7
+ export async function list() {
8
+ const cwd = process.cwd();
9
+ const config = await getConfig(cwd);
10
+ if (!config) {
11
+ console.log(chalk.red('Error: components.json not found.'));
12
+ console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
13
+ process.exit(1);
14
+ }
15
+ const uiBasePath = aliasToProjectPath(config.aliases.ui || 'src/components/ui');
16
+ const targetDir = resolveProjectPath(cwd, uiBasePath);
17
+ const statuses = [];
18
+ for (const name of getComponentNames()) {
19
+ const component = registry[name];
20
+ const allPresent = await allFilesExist(component.files, targetDir);
21
+ statuses.push({
22
+ name,
23
+ status: allPresent ? 'installed' : 'not installed',
24
+ });
25
+ }
26
+ const installed = statuses.filter(s => s.status === 'installed').sort((a, b) => a.name.localeCompare(b.name));
27
+ const notInstalled = statuses.filter(s => s.status === 'not installed').sort((a, b) => a.name.localeCompare(b.name));
28
+ console.log(chalk.bold(`\nInstalled (${installed.length}):`));
29
+ if (installed.length === 0) {
30
+ console.log(chalk.dim(' None'));
31
+ }
32
+ else {
33
+ for (const s of installed) {
34
+ console.log(chalk.green(' ✓ ') + s.name);
35
+ }
36
+ }
37
+ console.log(chalk.bold(`\nAvailable (${notInstalled.length}):`));
38
+ for (const s of notInstalled) {
39
+ console.log(chalk.dim(' - ') + s.name);
40
+ }
41
+ console.log('');
42
+ }
43
+ async function allFilesExist(files, targetDir) {
44
+ for (const file of files) {
45
+ if (!await fs.pathExists(path.join(targetDir, file))) {
46
+ return false;
47
+ }
48
+ }
49
+ return true;
50
+ }