@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.
@@ -1,314 +1,329 @@
1
- import fs from 'fs-extra';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import prompts from 'prompts';
5
- import chalk from 'chalk';
6
- import ora from 'ora';
7
- import { getDefaultConfig, type Config } from '../utils/config.js';
8
- import { getStylesTemplate } from '../templates/styles.js';
9
- import { getUtilsTemplate } from '../templates/utils.js';
10
- import { installPackages } from '../utils/package-manager.js';
11
- import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
12
-
13
- function getLibRegistryBaseUrl(branch: string) {
14
- return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/lib`;
15
- }
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- function getLocalLibDir(): string | null {
20
- const fromDist = path.resolve(__dirname, '../../../components/lib');
21
- if (fs.existsSync(fromDist)) {
22
- return fromDist;
23
- }
24
- return null;
25
- }
26
-
27
- async function fetchLibFileContent(file: string, branch: string): Promise<string> {
28
- const localLibDir = getLocalLibDir();
29
- if (localLibDir) {
30
- const localPath = path.join(localLibDir, file);
31
- if (await fs.pathExists(localPath)) {
32
- return fs.readFile(localPath, 'utf-8');
33
- }
34
- }
35
-
36
- const url = `${getLibRegistryBaseUrl(branch)}/${file}`;
37
- const response = await fetch(url);
38
- if (!response.ok) {
39
- throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
40
- }
41
- return response.text();
42
- }
43
-
44
- interface InitOptions {
45
- yes?: boolean;
46
- defaults?: boolean;
47
- branch: string;
48
- }
49
-
50
- function resolveProjectPath(cwd: string, inputPath: string): string {
51
- const resolved = path.resolve(cwd, inputPath);
52
- const relative = path.relative(cwd, resolved);
53
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
54
- throw new Error(`Path must stay inside the project directory: ${inputPath}`);
55
- }
56
- return resolved;
57
- }
58
-
59
- function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
60
- const normalized = aliasOrPath.startsWith('@/')
61
- ? path.join('src', aliasOrPath.slice(2))
62
- : aliasOrPath;
63
- return resolveProjectPath(cwd, normalized);
64
- }
65
-
66
- export async function init(options: InitOptions) {
67
- console.log(chalk.bold('\nšŸŽØ Welcome to shadcn-angular!\n'));
68
-
69
- const cwd = process.cwd();
70
-
71
- // Check if this is an Angular project
72
- const angularJsonPath = path.join(cwd, 'angular.json');
73
- if (!await fs.pathExists(angularJsonPath)) {
74
- console.log(chalk.red('Error: This does not appear to be an Angular project.'));
75
- console.log(chalk.dim('Please run this command in the root of your Angular project.'));
76
- process.exit(1);
77
- }
78
-
79
- // Check if already initialized
80
- const componentsJsonPath = path.join(cwd, 'components.json');
81
- if (await fs.pathExists(componentsJsonPath)) {
82
- const overwrite = options.yes
83
- ? true
84
- : (await prompts({
85
- type: 'confirm',
86
- name: 'overwrite',
87
- message: 'components.json already exists. Overwrite?',
88
- initial: false,
89
- })).overwrite;
90
- if (!overwrite) {
91
- console.log(chalk.dim('Initialization cancelled.'));
92
- return;
93
- }
94
- }
95
-
96
- let config: Config;
97
- let createShortcutRegistry = true;
98
-
99
- if (options.defaults || options.yes) {
100
- config = getDefaultConfig();
101
- } else {
102
- const THEME_COLORS: Record<string, string> = {
103
- zinc: '#71717a',
104
- slate: '#64748b',
105
- stone: '#78716c',
106
- gray: '#6b7280',
107
- neutral: '#737373',
108
- red: '#ef4444',
109
- rose: '#f43f5e',
110
- orange: '#f97316', // bright orange
111
- green: '#22c55e',
112
- blue: '#3b82f6',
113
- yellow: '#eab308',
114
- violet: '#8b5cf6',
115
- amber: '#d97706', // warm amber for preview
116
- };
117
-
118
- const themeChoices = [
119
- { title: 'Zinc', value: 'zinc' },
120
- { title: 'Slate', value: 'slate' },
121
- { title: 'Stone', value: 'stone' },
122
- { title: 'Gray', value: 'gray' },
123
- { title: 'Neutral', value: 'neutral' },
124
- { title: 'Red', value: 'red' },
125
- { title: 'Rose', value: 'rose' },
126
- { title: 'Orange', value: 'orange' },
127
- { title: 'Green', value: 'green' },
128
- { title: 'Blue', value: 'blue' },
129
- { title: 'Yellow', value: 'yellow' },
130
- { title: 'Violet', value: 'violet' },
131
- { title: 'Amber', value: 'amber' },
132
- ].map(c => ({
133
- ...c,
134
- title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
135
- }));
136
-
137
- const baseColorChoices = [
138
- { title: 'Neutral', value: 'neutral' },
139
- { title: 'Slate', value: 'slate' },
140
- { title: 'Stone', value: 'stone' },
141
- { title: 'Gray', value: 'gray' },
142
- { title: 'Zinc', value: 'zinc' },
143
- ].map(c => ({
144
- ...c,
145
- title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
146
- }));
147
-
148
- const responses = await prompts([
149
-
150
- {
151
- type: 'select',
152
- name: 'baseColor',
153
- message: 'Which color would you like to use as base color?',
154
- choices: baseColorChoices,
155
- initial: 0,
156
- },
157
- {
158
- type: 'select',
159
- name: 'theme',
160
- message: 'Which color would you like to use for the main theme?',
161
- choices: themeChoices,
162
- initial: (prev: string) => {
163
- const index = themeChoices.findIndex(c => c.value === prev);
164
- return index === -1 ? 0 : index;
165
- },
166
- },
167
- {
168
- type: 'text',
169
- name: 'componentsPath',
170
- message: 'Where would you like to install components?',
171
- initial: 'src/components/ui',
172
- },
173
- {
174
- type: 'text',
175
- name: 'utilsPath',
176
- message: 'Where would you like to install utils?',
177
- initial: 'src/components/lib',
178
- },
179
- {
180
- type: 'text',
181
- name: 'globalCss',
182
- message: 'Where is your global styles file?',
183
- initial: 'src/styles.scss',
184
- },
185
- {
186
- type: 'confirm',
187
- name: 'createShortcutRegistry',
188
- message: 'Would you like to create a shortcut registry scaffold?',
189
- initial: true,
190
- },
191
- ]);
192
-
193
- config = {
194
- $schema: 'https://shadcn-angular.dev/schema.json',
195
- style: 'default',
196
- tailwind: {
197
- css: responses.globalCss,
198
- baseColor: responses.baseColor,
199
- theme: responses.theme,
200
- cssVariables: true,
201
- },
202
- aliases: {
203
- components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
204
- utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
205
- ui: responses.componentsPath.replace('src/', '@/'),
206
- },
207
- };
208
- createShortcutRegistry = responses.createShortcutRegistry ?? true;
209
- }
210
-
211
- const spinner = ora('Initializing project...').start();
212
-
213
- try {
214
- // Write components.json
215
- await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
216
- spinner.text = 'Created components.json';
217
-
218
- // Create utils directory and file
219
- // Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
220
- // But we have the 'responses' object from CLI prompt only in the else block above!
221
- // So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
222
- // If config came from defaults, aliases are set.
223
- // We can reverse-map alias to path: @/ -> src/
224
-
225
- const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
226
-
227
- await fs.ensureDir(libDir);
228
- await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
229
- spinner.text = 'Created utils.ts';
230
-
231
- const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
232
- const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts', options.branch);
233
- await fs.writeFile(shortcutServicePath, shortcutServiceContent);
234
- spinner.text = 'Created shortcut-binding.service.ts';
235
-
236
- if (createShortcutRegistry) {
237
- await writeShortcutRegistryIndex(cwd, config, []);
238
- spinner.text = 'Created shortcut-registry.index.ts';
239
- }
240
-
241
- // Create tailwind.css file in the same directory as the global styles
242
- const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
243
- const stylesDir = path.dirname(userStylesPath);
244
- const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
245
-
246
- // Write the tailwind.css file with all Tailwind directives
247
- await fs.ensureDir(stylesDir);
248
- await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
249
- spinner.text = 'Created tailwind.css';
250
-
251
- // Add import to the user's global styles file if not already present
252
- let userStyles = await fs.pathExists(userStylesPath)
253
- ? await fs.readFile(userStylesPath, 'utf-8')
254
- : '';
255
-
256
- const tailwindImport = '@import "./tailwind.css";';
257
- if (!userStyles.includes('tailwind.css')) {
258
- // Add import at the top of the file
259
- userStyles = tailwindImport + '\n\n' + userStyles;
260
- await fs.writeFile(userStylesPath, userStyles);
261
- spinner.text = 'Added tailwind.css import to styles';
262
- }
263
-
264
- // Create components/ui directory
265
- const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
266
- await fs.ensureDir(uiDir);
267
- spinner.text = 'Created components directory';
268
-
269
- // Install dependencies
270
- spinner.text = 'Installing dependencies...';
271
- const dependencies = [
272
- 'clsx',
273
- 'tailwind-merge',
274
- 'class-variance-authority',
275
- 'tailwindcss',
276
- 'postcss',
277
- '@tailwindcss/postcss'
278
- ];
279
- await installPackages(dependencies, { cwd });
280
-
281
- // Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
282
- spinner.text = 'Configuring PostCSS...';
283
- const postcssrcPath = path.join(cwd, '.postcssrc.json');
284
-
285
- if (!await fs.pathExists(postcssrcPath)) {
286
- const configContent = {
287
- plugins: {
288
- '@tailwindcss/postcss': {}
289
- }
290
- };
291
- await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
292
- }
293
-
294
-
295
- spinner.succeed(chalk.green('Project initialized successfully!'));
296
-
297
- console.log('\n' + chalk.bold('Next steps:'));
298
- console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
299
- console.log(chalk.dim(' 2. Import and use in your templates'));
300
- console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
301
- console.log(chalk.dim(' "compilerOptions": {'));
302
- console.log(chalk.dim(' "baseUrl": ".",'));
303
- console.log(chalk.dim(' "paths": {'));
304
- console.log(chalk.dim(' "@/*": ["./src/*"]'));
305
- console.log(chalk.dim(' }'));
306
- console.log(chalk.dim(' }'));
307
- console.log('');
308
-
309
- } catch (error) {
310
- spinner.fail('Failed to initialize project');
311
- console.error(error);
312
- process.exit(1);
313
- }
314
- }
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { getDefaultConfig, type Config } from '../utils/config.js';
7
+ import { getStylesTemplate } from '../templates/styles.js';
8
+ import { getUtilsTemplate } from '../templates/utils.js';
9
+ import { installPackages } from '../utils/package-manager.js';
10
+ import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
11
+ import {
12
+ getLibRegistryBaseUrl,
13
+ getLocalLibDir,
14
+ resolveProjectPath,
15
+ aliasToProjectPath,
16
+ } from '../utils/paths.js';
17
+
18
+ const onCancel = () => {
19
+ console.log(chalk.dim('\nCancelled.'));
20
+ process.exit(0);
21
+ };
22
+
23
+ async function fetchLibFileContent(file: string, branch: string, remote?: boolean, registry?: string): Promise<string> {
24
+ const localLibDir = getLocalLibDir();
25
+ if (localLibDir && !remote) {
26
+ const localPath = path.join(localLibDir, file);
27
+ if (await fs.pathExists(localPath)) {
28
+ return fs.readFile(localPath, 'utf-8');
29
+ }
30
+ }
31
+
32
+ const url = `${getLibRegistryBaseUrl(branch, registry)}/${file}`;
33
+ const response = await fetch(url);
34
+ if (!response.ok) {
35
+ throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
36
+ }
37
+ return response.text();
38
+ }
39
+
40
+ interface InitOptions {
41
+ yes?: boolean;
42
+ defaults?: boolean;
43
+ remote?: boolean;
44
+ branch: string;
45
+ registry?: string;
46
+ }
47
+
48
+ interface InitConfig {
49
+ readonly config: Config;
50
+ readonly createShortcutRegistry: boolean;
51
+ }
52
+
53
+ function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
54
+ return resolveProjectPath(cwd, aliasToProjectPath(aliasOrPath));
55
+ }
56
+
57
+ function toAlias(inputPath: string): string {
58
+ return inputPath.startsWith('src/')
59
+ ? '@/' + inputPath.slice(4)
60
+ : inputPath;
61
+ }
62
+
63
+ async function promptForConfig(): Promise<InitConfig> {
64
+ const THEME_COLORS: Record<string, string> = {
65
+ zinc: '#71717a', slate: '#64748b', stone: '#78716c',
66
+ gray: '#6b7280', neutral: '#737373', red: '#ef4444',
67
+ rose: '#f43f5e', orange: '#f97316', green: '#22c55e',
68
+ blue: '#3b82f6', yellow: '#eab308', violet: '#8b5cf6',
69
+ amber: '#d97706',
70
+ };
71
+
72
+ const colorSwatch = (value: string, label: string) =>
73
+ `${chalk.hex(THEME_COLORS[value])('ā–ˆā–ˆ')} ${label}`;
74
+
75
+ const themeChoices = [
76
+ 'Zinc', 'Slate', 'Stone', 'Gray', 'Neutral',
77
+ 'Red', 'Rose', 'Orange', 'Green', 'Blue',
78
+ 'Yellow', 'Violet', 'Amber',
79
+ ].map(label => {
80
+ const value = label.toLowerCase();
81
+ return { title: colorSwatch(value, label), value };
82
+ });
83
+
84
+ const baseColorChoices = ['Neutral', 'Slate', 'Stone', 'Gray', 'Zinc'].map(label => {
85
+ const value = label.toLowerCase();
86
+ return { title: colorSwatch(value, label), value };
87
+ });
88
+
89
+ const responses = await prompts([
90
+ {
91
+ type: 'select',
92
+ name: 'baseColor',
93
+ message: 'Which color would you like to use as base color?',
94
+ choices: baseColorChoices,
95
+ initial: 0,
96
+ },
97
+ {
98
+ type: 'select',
99
+ name: 'theme',
100
+ message: 'Which color would you like to use for the main theme?',
101
+ choices: themeChoices,
102
+ initial: (prev: string) => {
103
+ const index = themeChoices.findIndex(c => c.value === prev);
104
+ return index === -1 ? 0 : index;
105
+ },
106
+ },
107
+ {
108
+ type: 'text',
109
+ name: 'componentsPath',
110
+ message: 'Where would you like to install components?',
111
+ initial: 'src/components/ui',
112
+ },
113
+ {
114
+ type: 'text',
115
+ name: 'utilsPath',
116
+ message: 'Where would you like to install utils?',
117
+ initial: 'src/components/lib',
118
+ },
119
+ {
120
+ type: 'text',
121
+ name: 'globalCss',
122
+ message: 'Where is your global styles file?',
123
+ initial: 'src/styles.scss',
124
+ },
125
+ {
126
+ type: 'confirm',
127
+ name: 'createShortcutRegistry',
128
+ message: 'Would you like to create a shortcut registry scaffold?',
129
+ initial: true,
130
+ },
131
+ ], { onCancel });
132
+
133
+ const componentsAlias = toAlias(responses.componentsPath);
134
+ const uiAlias = componentsAlias.endsWith('/ui')
135
+ ? componentsAlias
136
+ : componentsAlias + '/ui';
137
+
138
+ return {
139
+ config: {
140
+ style: 'default',
141
+ tailwind: {
142
+ css: responses.globalCss,
143
+ baseColor: responses.baseColor,
144
+ theme: responses.theme,
145
+ cssVariables: true,
146
+ },
147
+ aliases: {
148
+ components: componentsAlias.replace(/\/ui$/, ''),
149
+ utils: toAlias(responses.utilsPath),
150
+ ui: uiAlias,
151
+ },
152
+ },
153
+ createShortcutRegistry: responses.createShortcutRegistry ?? true,
154
+ };
155
+ }
156
+
157
+ async function installMissingDeps(cwd: string): Promise<void> {
158
+ const allDependencies = [
159
+ 'clsx', 'tailwind-merge', 'class-variance-authority',
160
+ 'tailwindcss', 'postcss', '@tailwindcss/postcss',
161
+ ];
162
+
163
+ const packageJsonPath = path.join(cwd, 'package.json');
164
+ let missingDeps = allDependencies;
165
+ if (await fs.pathExists(packageJsonPath)) {
166
+ const packageJson = await fs.readJson(packageJsonPath) as {
167
+ dependencies?: Record<string, string>;
168
+ devDependencies?: Record<string, string>;
169
+ };
170
+ const installed = { ...packageJson.dependencies, ...packageJson.devDependencies };
171
+ missingDeps = allDependencies.filter(dep => !installed[dep]);
172
+ }
173
+
174
+ if (missingDeps.length > 0) {
175
+ await installPackages(missingDeps, { cwd });
176
+ }
177
+ }
178
+
179
+ async function setupPostcss(cwd: string): Promise<void> {
180
+ const postcssConfigFiles = [
181
+ '.postcssrc.json', '.postcssrc.js', '.postcssrc.yaml',
182
+ 'postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs',
183
+ ];
184
+ const existing = (await Promise.all(
185
+ postcssConfigFiles.map(async f => await fs.pathExists(path.join(cwd, f)) ? f : null)
186
+ )).filter(Boolean);
187
+
188
+ if (existing.length === 0) {
189
+ await fs.writeJson(path.join(cwd, '.postcssrc.json'), {
190
+ plugins: { '@tailwindcss/postcss': {} },
191
+ }, { spaces: 4 });
192
+ return;
193
+ }
194
+
195
+ if (!existing.includes('.postcssrc.json')) {
196
+ console.log(chalk.yellow(` Existing PostCSS config found (${existing[0]}). Skipping .postcssrc.json creation.`));
197
+ console.log(chalk.dim(' Make sure @tailwindcss/postcss is configured in your PostCSS config.'));
198
+ }
199
+ }
200
+
201
+ async function autoConfigureTsconfig(cwd: string, spinner: ReturnType<typeof ora>): Promise<void> {
202
+ const tsconfigPath = path.join(cwd, 'tsconfig.json');
203
+ if (!await fs.pathExists(tsconfigPath)) return;
204
+
205
+ try {
206
+ const raw = await fs.readFile(tsconfigPath, 'utf-8');
207
+ const stripped = raw
208
+ .replaceAll(/\/\/.*$/gm, '')
209
+ .replaceAll(/\/\*[\s\S]*?\*\//g, '');
210
+ const tsconfig = JSON.parse(stripped) as {
211
+ compilerOptions?: { baseUrl?: string; paths?: Record<string, string[]> };
212
+ };
213
+
214
+ const compilerOptions = tsconfig.compilerOptions ??= {};
215
+ const paths = compilerOptions.paths ??= {};
216
+
217
+ if (paths['@/*']) return;
218
+
219
+ if (!compilerOptions.baseUrl) {
220
+ compilerOptions.baseUrl = '.';
221
+ }
222
+ paths['@/*'] = ['./src/*'];
223
+
224
+ await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
225
+ spinner.text = 'Configured tsconfig.json paths';
226
+ } catch {
227
+ console.log(chalk.dim(' Could not auto-configure tsconfig.json — please add "@/*": ["./src/*"] to paths manually.'));
228
+ }
229
+ }
230
+
231
+ export async function init(options: InitOptions) {
232
+ console.log(chalk.bold('\nšŸŽØ Welcome to shadcn-angular!\n'));
233
+
234
+ const cwd = process.cwd();
235
+
236
+ const angularJsonPath = path.join(cwd, 'angular.json');
237
+ if (!await fs.pathExists(angularJsonPath)) {
238
+ console.log(chalk.red('Error: This does not appear to be an Angular project.'));
239
+ console.log(chalk.dim('Please run this command in the root of your Angular project.'));
240
+ process.exit(1);
241
+ }
242
+
243
+ const componentsJsonPath = path.join(cwd, 'components.json');
244
+
245
+ if (await fs.pathExists(componentsJsonPath)) {
246
+ const overwrite = options.yes
247
+ ? true
248
+ : (await prompts({
249
+ type: 'confirm',
250
+ name: 'overwrite',
251
+ message: 'components.json already exists. Overwrite?',
252
+ initial: false,
253
+ }, { onCancel })).overwrite;
254
+ if (!overwrite) {
255
+ console.log(chalk.dim('Initialization cancelled.'));
256
+ return;
257
+ }
258
+ }
259
+
260
+ const { config, createShortcutRegistry } = options.defaults || options.yes
261
+ ? { config: getDefaultConfig(), createShortcutRegistry: true }
262
+ : await promptForConfig();
263
+
264
+ if (options.registry) {
265
+ config.registry = options.registry;
266
+ }
267
+
268
+ const spinner = ora('Initializing project...').start();
269
+
270
+ try {
271
+ await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
272
+ spinner.text = 'Created components.json';
273
+
274
+ const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
275
+ await fs.ensureDir(libDir);
276
+ await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
277
+ spinner.text = 'Created utils.ts';
278
+
279
+ if (createShortcutRegistry) {
280
+ const content = await fetchLibFileContent('shortcut-binding.service.ts', options.branch, options.remote, options.registry);
281
+ await fs.writeFile(path.join(libDir, 'shortcut-binding.service.ts'), content);
282
+ spinner.text = 'Created shortcut-binding.service.ts';
283
+
284
+ await writeShortcutRegistryIndex(cwd, config, []);
285
+ spinner.text = 'Created shortcut-registry.index.ts';
286
+ }
287
+
288
+ const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
289
+ const stylesDir = path.dirname(userStylesPath);
290
+
291
+ await fs.ensureDir(stylesDir);
292
+ await fs.writeFile(path.join(stylesDir, 'tailwind.css'), getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
293
+ spinner.text = 'Created tailwind.css';
294
+
295
+ let userStyles = await fs.pathExists(userStylesPath)
296
+ ? await fs.readFile(userStylesPath, 'utf-8')
297
+ : '';
298
+
299
+ if (!userStyles.includes('tailwind.css')) {
300
+ userStyles = '@import "./tailwind.css";\n\n' + userStyles;
301
+ await fs.writeFile(userStylesPath, userStyles);
302
+ spinner.text = 'Added tailwind.css import to styles';
303
+ }
304
+
305
+ const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
306
+ await fs.ensureDir(uiDir);
307
+ spinner.text = 'Created components directory';
308
+
309
+ spinner.text = 'Installing dependencies...';
310
+ await installMissingDeps(cwd);
311
+
312
+ spinner.text = 'Configuring PostCSS...';
313
+ await setupPostcss(cwd);
314
+
315
+ await autoConfigureTsconfig(cwd, spinner);
316
+
317
+ spinner.succeed(chalk.green('Project initialized successfully!'));
318
+
319
+ console.log('\n' + chalk.bold('Next steps:'));
320
+ console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
321
+ console.log(chalk.dim(' 2. Import and use in your templates'));
322
+ console.log('');
323
+
324
+ } catch (error) {
325
+ spinner.fail('Failed to initialize project');
326
+ console.error(error);
327
+ process.exit(1);
328
+ }
329
+ }