create-ng-tailwind 3.1.0 → 4.1.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.

Potentially problematic release.


This version of create-ng-tailwind might be problematic. Click here for more details.

Files changed (48) hide show
  1. package/CHANGELOG.md +96 -341
  2. package/README.md +111 -157
  3. package/lib/cli/index.js +74 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +2 -5
  6. package/lib/templates/base/components.js +243 -0
  7. package/lib/templates/base/index.js +207 -0
  8. package/lib/templates/base/infrastructure.js +314 -0
  9. package/lib/templates/base/linting.js +359 -0
  10. package/lib/templates/base/pwa.js +103 -0
  11. package/lib/templates/base/services.js +362 -0
  12. package/lib/templates/blog/app.js +250 -0
  13. package/lib/templates/blog/components.js +360 -0
  14. package/lib/templates/blog/i18n.js +77 -0
  15. package/lib/templates/blog/index.js +126 -0
  16. package/lib/templates/blog/pages.js +554 -0
  17. package/lib/templates/blog/services.js +390 -0
  18. package/lib/templates/dashboard/app.js +320 -0
  19. package/lib/templates/dashboard/charts.js +305 -0
  20. package/lib/templates/dashboard/components.js +410 -0
  21. package/lib/templates/dashboard/i18n.js +340 -0
  22. package/lib/templates/dashboard/index.js +141 -0
  23. package/lib/templates/dashboard/layout.js +310 -0
  24. package/lib/templates/dashboard/pages.js +681 -0
  25. package/lib/templates/ecommerce/app.js +315 -0
  26. package/lib/templates/ecommerce/components.js +496 -0
  27. package/lib/templates/ecommerce/i18n.js +389 -0
  28. package/lib/templates/ecommerce/index.js +152 -0
  29. package/lib/templates/ecommerce/layout.js +270 -0
  30. package/lib/templates/ecommerce/pages.js +969 -0
  31. package/lib/templates/ecommerce/services.js +300 -0
  32. package/lib/templates/index.js +12 -0
  33. package/lib/templates/landing/index.js +1117 -0
  34. package/lib/templates/portfolio/index.js +1160 -0
  35. package/lib/templates/saas/index.js +1371 -0
  36. package/lib/templates/starter/app.js +364 -0
  37. package/lib/templates/starter/i18n.js +856 -0
  38. package/lib/templates/starter/index.js +52 -4060
  39. package/lib/templates/starter/layout.js +852 -0
  40. package/lib/templates/starter/pages.js +1241 -0
  41. package/lib/utils/nodeCompat.js +85 -0
  42. package/package.json +1 -1
  43. package/lib/templates/starter/features.js +0 -867
  44. package/lib/utils/ai-config.js +0 -641
  45. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  47. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  48. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,314 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create directory structure for base template
6
+ */
7
+ async function createDirectoryStructure(config) {
8
+ const directories = [
9
+ 'src/environments',
10
+ 'src/app/core/services',
11
+ 'src/app/core/guards',
12
+ 'src/app/core/interceptors',
13
+ 'src/app/core/utils',
14
+ 'src/app/shared/components/button',
15
+ 'src/app/shared/components/card',
16
+ 'src/app/shared/components/loading-spinner',
17
+ 'src/app/shared/components/toast',
18
+ 'src/app/shared/components/modal',
19
+ 'src/app/shared/pipes',
20
+ 'src/app/shared/directives',
21
+ 'src/app/shared/models',
22
+ 'public/assets/images',
23
+ 'public/assets/icons',
24
+ ];
25
+
26
+ for (const dir of directories) {
27
+ await fs.ensureDir(path.join(config.fullPath, dir));
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Create environment files
33
+ */
34
+ async function createEnvironments(config) {
35
+ // Development environment
36
+ const devEnvironment = `export const environment = {
37
+ production: false,
38
+ apiUrl: 'http://localhost:3000/api',
39
+ appName: '${config.projectName}',
40
+ version: '1.0.0',
41
+ enableDevTools: true,
42
+ logLevel: 'debug' as 'debug' | 'info' | 'warn' | 'error',
43
+ features: {
44
+ enableAnalytics: false,
45
+ enableLogging: true,
46
+ enableMocking: true
47
+ },
48
+ auth: {
49
+ tokenKey: 'auth_token',
50
+ refreshTokenKey: 'refresh_token',
51
+ tokenExpirationBuffer: 300000 // 5 minutes
52
+ },
53
+ api: {
54
+ timeout: 30000,
55
+ retryAttempts: 3
56
+ }
57
+ };`;
58
+
59
+ await fs.writeFile(
60
+ path.join(config.fullPath, 'src/environments/environment.ts'),
61
+ devEnvironment
62
+ );
63
+
64
+ // Production environment
65
+ const prodEnvironment = `export const environment = {
66
+ production: true,
67
+ apiUrl: 'https://api.${config.projectName}.com/api',
68
+ appName: '${config.projectName}',
69
+ version: '1.0.0',
70
+ enableDevTools: false,
71
+ logLevel: 'error' as 'debug' | 'info' | 'warn' | 'error',
72
+ features: {
73
+ enableAnalytics: true,
74
+ enableLogging: false,
75
+ enableMocking: false
76
+ },
77
+ auth: {
78
+ tokenKey: 'auth_token',
79
+ refreshTokenKey: 'refresh_token',
80
+ tokenExpirationBuffer: 300000 // 5 minutes
81
+ },
82
+ api: {
83
+ timeout: 30000,
84
+ retryAttempts: 1
85
+ }
86
+ };`;
87
+
88
+ await fs.writeFile(
89
+ path.join(config.fullPath, 'src/environments/environment.prod.ts'),
90
+ prodEnvironment
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Create auth guard
96
+ */
97
+ async function createGuards(config) {
98
+ const authGuard = `import { CanActivateFn, Router } from '@angular/router';
99
+ import { inject } from '@angular/core';
100
+
101
+ /**
102
+ * Simple authentication guard
103
+ * Replace with your actual authentication logic
104
+ */
105
+ export const authGuard: CanActivateFn = (_route, _state) => {
106
+ const router = inject(Router);
107
+
108
+ // Replace with your actual authentication check
109
+ const isAuthenticated = localStorage.getItem('auth-token') !== null;
110
+
111
+ if (!isAuthenticated) {
112
+ // Redirect to login page
113
+ router.navigate(['/auth/login']);
114
+ return false;
115
+ }
116
+
117
+ return true;
118
+ };
119
+
120
+ /**
121
+ * Example: Check if user has specific role
122
+ */
123
+ export const roleGuard = (requiredRole: string): CanActivateFn => {
124
+ return (_route, _state) => {
125
+ const router = inject(Router);
126
+
127
+ // Replace with your actual role checking logic
128
+ const userRole = localStorage.getItem('user-role');
129
+
130
+ if (userRole !== requiredRole) {
131
+ router.navigate(['/unauthorized']);
132
+ return false;
133
+ }
134
+
135
+ return true;
136
+ };
137
+ };`;
138
+
139
+ await fs.writeFile(path.join(config.fullPath, 'src/app/core/guards/auth.guard.ts'), authGuard);
140
+ }
141
+
142
+ /**
143
+ * Create model interfaces
144
+ */
145
+ async function createModels(config) {
146
+ // User Interface
147
+ const userInterface = `export interface User {
148
+ id: string;
149
+ email: string;
150
+ firstName: string;
151
+ lastName: string;
152
+ avatar?: string;
153
+ role: 'user' | 'admin' | 'moderator';
154
+ createdAt: Date;
155
+ updatedAt: Date;
156
+ isActive: boolean;
157
+ }
158
+
159
+ export interface UserProfile {
160
+ user: User;
161
+ preferences: {
162
+ notifications: boolean;
163
+ language: string;
164
+ };
165
+ }
166
+
167
+ export interface CreateUserRequest {
168
+ email: string;
169
+ password: string;
170
+ firstName: string;
171
+ lastName: string;
172
+ }
173
+
174
+ export interface UpdateUserRequest {
175
+ firstName?: string;
176
+ lastName?: string;
177
+ avatar?: string;
178
+ }`;
179
+
180
+ await fs.writeFile(
181
+ path.join(config.fullPath, 'src/app/shared/models/user.interface.ts'),
182
+ userInterface
183
+ );
184
+
185
+ // API Response Interface
186
+ const apiResponseInterface = `export interface ApiResponse<T = unknown> {
187
+ data: T;
188
+ message: string;
189
+ success: boolean;
190
+ errors?: string[];
191
+ meta?: {
192
+ total: number;
193
+ page: number;
194
+ perPage: number;
195
+ totalPages: number;
196
+ };
197
+ }
198
+
199
+ export interface ApiError {
200
+ message: string;
201
+ code: string;
202
+ status: number;
203
+ timestamp: string;
204
+ details?: Record<string, string[]>;
205
+ }
206
+
207
+ export interface PaginatedResponse<T> {
208
+ data: T[];
209
+ meta: {
210
+ total: number;
211
+ page: number;
212
+ perPage: number;
213
+ totalPages: number;
214
+ hasNextPage: boolean;
215
+ hasPrevPage: boolean;
216
+ };
217
+ }`;
218
+
219
+ await fs.writeFile(
220
+ path.join(config.fullPath, 'src/app/shared/models/api-response.interface.ts'),
221
+ apiResponseInterface
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Create pipes (Truncate, TimeAgo)
227
+ */
228
+ async function createPipes(config) {
229
+ // Truncate Pipe
230
+ const truncatePipe = `import { Pipe, PipeTransform } from '@angular/core';
231
+
232
+ @Pipe({
233
+ name: 'truncate',
234
+ standalone: true
235
+ })
236
+ export class TruncatePipe implements PipeTransform {
237
+ transform(value: string | null | undefined, limit = 50, suffix = '...'): string {
238
+ if (!value) return '';
239
+
240
+ if (value.length <= limit) {
241
+ return value;
242
+ }
243
+
244
+ return value.substring(0, limit).trim() + suffix;
245
+ }
246
+ }`;
247
+
248
+ await fs.writeFile(
249
+ path.join(config.fullPath, 'src/app/shared/pipes/truncate.pipe.ts'),
250
+ truncatePipe
251
+ );
252
+
253
+ // Time Ago Pipe
254
+ const timeAgoPipe = `import { Pipe, PipeTransform } from '@angular/core';
255
+
256
+ @Pipe({
257
+ name: 'timeAgo',
258
+ standalone: true
259
+ })
260
+ export class TimeAgoPipe implements PipeTransform {
261
+ transform(value: string | number | Date | null | undefined): string {
262
+ if (!value) return '';
263
+
264
+ const date = new Date(value);
265
+ const now = new Date();
266
+ const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1000);
267
+
268
+ if (secondsAgo < 60) {
269
+ return 'just now';
270
+ }
271
+
272
+ const minutesAgo = Math.floor(secondsAgo / 60);
273
+ if (minutesAgo < 60) {
274
+ return \`\${minutesAgo} minute\${minutesAgo === 1 ? '' : 's'} ago\`;
275
+ }
276
+
277
+ const hoursAgo = Math.floor(minutesAgo / 60);
278
+ if (hoursAgo < 24) {
279
+ return \`\${hoursAgo} hour\${hoursAgo === 1 ? '' : 's'} ago\`;
280
+ }
281
+
282
+ const daysAgo = Math.floor(hoursAgo / 24);
283
+ if (daysAgo < 7) {
284
+ return \`\${daysAgo} day\${daysAgo === 1 ? '' : 's'} ago\`;
285
+ }
286
+
287
+ const weeksAgo = Math.floor(daysAgo / 7);
288
+ if (weeksAgo < 4) {
289
+ return \`\${weeksAgo} week\${weeksAgo === 1 ? '' : 's'} ago\`;
290
+ }
291
+
292
+ const monthsAgo = Math.floor(daysAgo / 30);
293
+ if (monthsAgo < 12) {
294
+ return \`\${monthsAgo} month\${monthsAgo === 1 ? '' : 's'} ago\`;
295
+ }
296
+
297
+ const yearsAgo = Math.floor(daysAgo / 365);
298
+ return \`\${yearsAgo} year\${yearsAgo === 1 ? '' : 's'} ago\`;
299
+ }
300
+ }`;
301
+
302
+ await fs.writeFile(
303
+ path.join(config.fullPath, 'src/app/shared/pipes/time-ago.pipe.ts'),
304
+ timeAgoPipe
305
+ );
306
+ }
307
+
308
+ module.exports = {
309
+ createDirectoryStructure,
310
+ createEnvironments,
311
+ createGuards,
312
+ createModels,
313
+ createPipes,
314
+ };
@@ -0,0 +1,359 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Create VS Code settings and extensions
6
+ */
7
+ async function createVSCodeSettings(config) {
8
+ // Create .vscode directory
9
+ await fs.ensureDir(path.join(config.fullPath, '.vscode'));
10
+
11
+ // VSCode settings for format on save
12
+ const vscodeSettings = {
13
+ 'editor.formatOnSave': true,
14
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
15
+ 'editor.codeActionsOnSave': {
16
+ 'source.fixAll.eslint': 'explicit',
17
+ },
18
+ '[typescript]': {
19
+ 'editor.formatOnSave': true,
20
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
21
+ },
22
+ '[html]': {
23
+ 'editor.formatOnSave': true,
24
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
25
+ },
26
+ '[css]': {
27
+ 'editor.formatOnSave': true,
28
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
29
+ },
30
+ '[json]': {
31
+ 'editor.formatOnSave': true,
32
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
33
+ },
34
+ '[jsonc]': {
35
+ 'editor.formatOnSave': true,
36
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
37
+ },
38
+ 'files.eol': '\n',
39
+ 'files.trimTrailingWhitespace': true,
40
+ 'files.insertFinalNewline': true,
41
+ 'editor.tabSize': 2,
42
+ 'prettier.requireConfig': false,
43
+ 'eslint.validate': ['javascript', 'typescript', 'html'],
44
+ // Suppress CSS linting warnings for Tailwind CSS directives
45
+ 'css.lint.unknownAtRules': 'ignore',
46
+ };
47
+
48
+ // VSCode extensions recommendations
49
+ const vscodeExtensions = {
50
+ recommendations: [
51
+ 'esbenp.prettier-vscode',
52
+ 'dbaeumer.vscode-eslint',
53
+ 'angular.ng-template',
54
+ 'bradlc.vscode-tailwindcss',
55
+ ],
56
+ };
57
+
58
+ // Write settings.json
59
+ await fs.writeFile(
60
+ path.join(config.fullPath, '.vscode/settings.json'),
61
+ JSON.stringify(vscodeSettings, null, 2)
62
+ );
63
+
64
+ // Write extensions.json
65
+ await fs.writeFile(
66
+ path.join(config.fullPath, '.vscode/extensions.json'),
67
+ JSON.stringify(vscodeExtensions, null, 2)
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Update TypeScript config with path mappings
73
+ */
74
+ async function updateTsConfig(config) {
75
+ // Define path mapping configuration
76
+ const pathMappingConfig = ` "baseUrl": "./",
77
+ "paths": {
78
+ "@/*": ["src/*"],
79
+ "@app/*": ["src/app/*"],
80
+ "@core/*": ["src/app/core/*"],
81
+ "@shared/*": ["src/app/shared/*"],
82
+ "@features/*": ["src/app/features/*"],
83
+ "@layout/*": ["src/app/layout/*"],
84
+ "@environments/*": ["src/environments/*"]
85
+ },`;
86
+
87
+ // Helper function to add path mappings to a tsconfig file
88
+ const addPathMappings = (content) => {
89
+ if (content.includes('"compilerOptions": {')) {
90
+ const compilerOptionsStart =
91
+ content.indexOf('"compilerOptions": {') + '"compilerOptions": {'.length;
92
+ const beforeOptions = content.substring(0, compilerOptionsStart);
93
+ const afterOptions = content.substring(compilerOptionsStart);
94
+ return beforeOptions + '\n' + pathMappingConfig + afterOptions;
95
+ }
96
+ return content;
97
+ };
98
+
99
+ // Update tsconfig.json (base config) - CRITICAL for IDE support
100
+ const tsConfigPath = path.join(config.fullPath, 'tsconfig.json');
101
+ let tsConfigContent = await fs.readFile(tsConfigPath, 'utf8');
102
+ tsConfigContent = addPathMappings(tsConfigContent);
103
+ await fs.writeFile(tsConfigPath, tsConfigContent);
104
+
105
+ // Update tsconfig.app.json
106
+ const tsConfigAppPath = path.join(config.fullPath, 'tsconfig.app.json');
107
+ let tsConfigAppContent = await fs.readFile(tsConfigAppPath, 'utf8');
108
+ tsConfigAppContent = addPathMappings(tsConfigAppContent);
109
+ await fs.writeFile(tsConfigAppPath, tsConfigAppContent);
110
+
111
+ // Update tsconfig.spec.json
112
+ const tsConfigSpecPath = path.join(config.fullPath, 'tsconfig.spec.json');
113
+ let tsConfigSpecContent = await fs.readFile(tsConfigSpecPath, 'utf8');
114
+ tsConfigSpecContent = addPathMappings(tsConfigSpecContent);
115
+ await fs.writeFile(tsConfigSpecPath, tsConfigSpecContent);
116
+ }
117
+
118
+ /**
119
+ * Configure angular.json for ESLint
120
+ */
121
+ async function configureAngularJson(config) {
122
+ const angularJsonPath = path.join(config.fullPath, 'angular.json');
123
+ const angularJson = JSON.parse(await fs.readFile(angularJsonPath, 'utf8'));
124
+
125
+ // Add lint architect target
126
+ if (angularJson.projects && angularJson.projects[config.projectName]) {
127
+ angularJson.projects[config.projectName].architect.lint = {
128
+ builder: '@angular-eslint/builder:lint',
129
+ options: {
130
+ lintFilePatterns: ['src/**/*.ts', 'src/**/*.html'],
131
+ },
132
+ };
133
+ }
134
+
135
+ await fs.writeFile(angularJsonPath, JSON.stringify(angularJson, null, 2));
136
+ }
137
+
138
+ /**
139
+ * Install linting packages
140
+ */
141
+ async function installLintingPackages(config) {
142
+ const execa = require('execa');
143
+
144
+ try {
145
+ const packages = [
146
+ '@eslint/js@^9.23.0',
147
+ 'eslint@^9.23.0',
148
+ 'angular-eslint@19.3.0',
149
+ 'typescript-eslint@^8.20.0',
150
+ 'prettier@^3.5.3',
151
+ 'eslint-config-prettier@^10.1.2',
152
+ 'prettier-plugin-tailwindcss@^0.6.11',
153
+ 'simple-git-hooks@^2.11.1',
154
+ ];
155
+
156
+ await execa.command(`npm install ${packages.join(' ')} --save-dev`, {
157
+ cwd: config.fullPath,
158
+ stdio: 'pipe',
159
+ });
160
+ } catch (error) {
161
+ // Silently fail - packages will be installed when user runs npm install
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Setup linting (ESLint, Prettier, simple-git-hooks)
167
+ */
168
+ async function setupLinting(config) {
169
+ // Read current package.json
170
+ const packageJsonPath = path.join(config.fullPath, 'package.json');
171
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
172
+
173
+ // Add icons and PWA to regular dependencies
174
+ if (!packageJson.dependencies) {
175
+ packageJson.dependencies = {};
176
+ }
177
+ packageJson.dependencies['@ng-icons/core'] = '^29.7.0';
178
+ packageJson.dependencies['@ng-icons/heroicons'] = '^29.7.0';
179
+ packageJson.dependencies['@angular/service-worker'] = packageJson.dependencies['@angular/core'];
180
+
181
+ // Add linting dev dependencies
182
+ const lintingDependencies = {
183
+ '@eslint/js': '^9.23.0',
184
+ eslint: '^9.23.0',
185
+ 'angular-eslint': '19.3.0',
186
+ 'typescript-eslint': '^8.20.0',
187
+ prettier: '^3.5.3',
188
+ 'eslint-config-prettier': '^10.1.2',
189
+ 'prettier-plugin-tailwindcss': '^0.6.11',
190
+ 'simple-git-hooks': '^2.11.1',
191
+ };
192
+
193
+ // Add enhanced scripts
194
+ const enhancedScripts = {
195
+ lint: 'ng lint',
196
+ 'lint:fix': 'ng lint --fix',
197
+ format: 'prettier --write "**/*.{ts,html,css,json,md}"',
198
+ 'format:check': 'prettier --check "**/*.{ts,html,css,json,md}"',
199
+ 'format:ts': 'prettier --write "**/*.ts"',
200
+ 'code:check': 'npm run format:check && npm run lint',
201
+ 'code:fix': 'npm run format && npm run lint:fix',
202
+ prepare: 'simple-git-hooks',
203
+ };
204
+
205
+ // Add prettier configuration
206
+ const prettierConfig = {
207
+ printWidth: 100,
208
+ singleQuote: true,
209
+ trailingComma: 'es5',
210
+ tabWidth: 2,
211
+ semi: true,
212
+ arrowParens: 'always',
213
+ endOfLine: 'lf',
214
+ plugins: ['prettier-plugin-tailwindcss'],
215
+ overrides: [
216
+ {
217
+ files: '*.html',
218
+ options: {
219
+ parser: 'angular',
220
+ },
221
+ },
222
+ ],
223
+ };
224
+
225
+ // simple-git-hooks configuration (lightweight pre-commit hooks)
226
+ const simpleGitHooksConfig = {
227
+ 'pre-commit': 'npm run lint',
228
+ };
229
+
230
+ // Update package.json
231
+ packageJson.devDependencies = {
232
+ ...packageJson.devDependencies,
233
+ ...lintingDependencies,
234
+ };
235
+ packageJson.scripts = { ...packageJson.scripts, ...enhancedScripts };
236
+ packageJson.prettier = prettierConfig;
237
+ packageJson['simple-git-hooks'] = simpleGitHooksConfig;
238
+
239
+ // Write updated package.json
240
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
241
+
242
+ // Configure angular.json for ESLint
243
+ await configureAngularJson(config);
244
+
245
+ // Create ESLint configuration (ESLint 9+ flat config)
246
+ const eslintConfig = `// @ts-check
247
+ const eslint = require("@eslint/js");
248
+ const tseslint = require("typescript-eslint");
249
+ const angular = require("angular-eslint");
250
+ const eslintConfigPrettier = require("eslint-config-prettier");
251
+
252
+ module.exports = tseslint.config(
253
+ {
254
+ files: ["**/*.ts"],
255
+ extends: [
256
+ eslint.configs.recommended,
257
+ ...tseslint.configs.recommended,
258
+ ...tseslint.configs.stylistic,
259
+ ...angular.configs.tsRecommended,
260
+ eslintConfigPrettier,
261
+ ],
262
+ processor: angular.processInlineTemplates,
263
+ rules: {
264
+ "@angular-eslint/directive-selector": [
265
+ "error",
266
+ {
267
+ type: "attribute",
268
+ prefix: "app",
269
+ style: "camelCase",
270
+ },
271
+ ],
272
+ "@angular-eslint/component-selector": [
273
+ "error",
274
+ {
275
+ type: "element",
276
+ prefix: "app",
277
+ style: "kebab-case",
278
+ },
279
+ ],
280
+ "@angular-eslint/component-class-suffix": [
281
+ "error",
282
+ {
283
+ "suffixes": ["Component", "App"]
284
+ }
285
+ ],
286
+ "@typescript-eslint/no-unused-vars": [
287
+ "error",
288
+ {
289
+ "argsIgnorePattern": "^_",
290
+ "varsIgnorePattern": "^_"
291
+ }
292
+ ],
293
+ },
294
+ },
295
+ {
296
+ files: ["**/*.html"],
297
+ extends: [
298
+ ...angular.configs.templateRecommended,
299
+ ...angular.configs.templateAccessibility,
300
+ ],
301
+ rules: {},
302
+ }
303
+ );
304
+ `;
305
+
306
+ await fs.writeFile(path.join(config.fullPath, 'eslint.config.js'), eslintConfig);
307
+
308
+ // Create .prettierignore file
309
+ const prettierIgnore = `# Build outputs
310
+ dist/
311
+ coverage/
312
+ node_modules/
313
+
314
+ # Generated files
315
+ *.d.ts
316
+ `;
317
+
318
+ await fs.writeFile(path.join(config.fullPath, '.prettierignore'), prettierIgnore);
319
+
320
+ // Create .prettierrc.json file for better IDE support
321
+ await fs.writeFile(
322
+ path.join(config.fullPath, '.prettierrc.json'),
323
+ JSON.stringify(prettierConfig, null, 2)
324
+ );
325
+
326
+ // Install linting packages if not skipping install
327
+ if (!config.skipInstall) {
328
+ await installLintingPackages(config);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Format code using Prettier
334
+ */
335
+ async function formatCode(config) {
336
+ const execa = require('execa');
337
+
338
+ try {
339
+ // Run prettier to format all generated files (including config files)
340
+ await execa.command(
341
+ 'npx prettier --write "**/*.{ts,html,css,json}" --ignore-path .gitignore --log-level silent',
342
+ {
343
+ cwd: config.fullPath,
344
+ stdio: 'pipe',
345
+ }
346
+ );
347
+ } catch (error) {
348
+ // If prettier fails, it's not critical - user can run it manually
349
+ }
350
+ }
351
+
352
+ module.exports = {
353
+ createVSCodeSettings,
354
+ updateTsConfig,
355
+ configureAngularJson,
356
+ installLintingPackages,
357
+ setupLinting,
358
+ formatCode,
359
+ };