nexu-app 2.0.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.
Files changed (106) hide show
  1. package/README.md +149 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1192 -0
  4. package/package.json +43 -0
  5. package/templates/default/.changeset/config.json +11 -0
  6. package/templates/default/.eslintignore +16 -0
  7. package/templates/default/.eslintrc.js +67 -0
  8. package/templates/default/.github/actions/build/action.yml +35 -0
  9. package/templates/default/.github/actions/quality/action.yml +53 -0
  10. package/templates/default/.github/dependabot.yml +51 -0
  11. package/templates/default/.github/workflows/deploy-dev.yml +83 -0
  12. package/templates/default/.github/workflows/deploy-prod.yml +83 -0
  13. package/templates/default/.github/workflows/deploy-rec.yml +83 -0
  14. package/templates/default/.husky/commit-msg +1 -0
  15. package/templates/default/.husky/pre-commit +1 -0
  16. package/templates/default/.nexu-version +1 -0
  17. package/templates/default/.prettierignore +7 -0
  18. package/templates/default/.prettierrc +19 -0
  19. package/templates/default/.vscode/extensions.json +14 -0
  20. package/templates/default/.vscode/settings.json +36 -0
  21. package/templates/default/apps/gitkeep +0 -0
  22. package/templates/default/commitlint.config.js +26 -0
  23. package/templates/default/docker/docker-compose.dev.yml +49 -0
  24. package/templates/default/docker/docker-compose.prod.yml +64 -0
  25. package/templates/default/docker/docker-compose.yml +6 -0
  26. package/templates/default/docs/architecture.md +452 -0
  27. package/templates/default/docs/cli.md +330 -0
  28. package/templates/default/docs/contributing.md +462 -0
  29. package/templates/default/docs/scripts.md +460 -0
  30. package/templates/default/gitignore +44 -0
  31. package/templates/default/lintstagedrc.cjs +4 -0
  32. package/templates/default/package.json +51 -0
  33. package/templates/default/packages/auth/package.json +61 -0
  34. package/templates/default/packages/auth/src/components/ProtectedRoute.tsx +75 -0
  35. package/templates/default/packages/auth/src/components/SignInForm.tsx +153 -0
  36. package/templates/default/packages/auth/src/components/SignUpForm.tsx +179 -0
  37. package/templates/default/packages/auth/src/components/SocialButtons.tsx +147 -0
  38. package/templates/default/packages/auth/src/components/index.ts +4 -0
  39. package/templates/default/packages/auth/src/hooks/index.ts +4 -0
  40. package/templates/default/packages/auth/src/hooks/useAuth.ts +51 -0
  41. package/templates/default/packages/auth/src/hooks/useRequireAuth.ts +54 -0
  42. package/templates/default/packages/auth/src/hooks/useSession.ts +48 -0
  43. package/templates/default/packages/auth/src/hooks/useUser.ts +48 -0
  44. package/templates/default/packages/auth/src/index.ts +45 -0
  45. package/templates/default/packages/auth/src/next/index.ts +18 -0
  46. package/templates/default/packages/auth/src/next/middleware.ts +183 -0
  47. package/templates/default/packages/auth/src/next/server.ts +219 -0
  48. package/templates/default/packages/auth/src/providers/AuthContext.tsx +435 -0
  49. package/templates/default/packages/auth/src/providers/index.ts +1 -0
  50. package/templates/default/packages/auth/src/types/index.ts +284 -0
  51. package/templates/default/packages/auth/src/utils/api.ts +228 -0
  52. package/templates/default/packages/auth/src/utils/index.ts +3 -0
  53. package/templates/default/packages/auth/src/utils/oauth.ts +230 -0
  54. package/templates/default/packages/auth/src/utils/token.ts +204 -0
  55. package/templates/default/packages/auth/tsconfig.json +14 -0
  56. package/templates/default/packages/auth/tsup.config.ts +18 -0
  57. package/templates/default/packages/cache/package.json +26 -0
  58. package/templates/default/packages/cache/src/index.ts +137 -0
  59. package/templates/default/packages/cache/tsconfig.json +9 -0
  60. package/templates/default/packages/cache/tsup.config.ts +9 -0
  61. package/templates/default/packages/config/eslint/index.js +20 -0
  62. package/templates/default/packages/config/package.json +9 -0
  63. package/templates/default/packages/config/typescript/base.json +26 -0
  64. package/templates/default/packages/constants/package.json +26 -0
  65. package/templates/default/packages/constants/src/index.ts +121 -0
  66. package/templates/default/packages/constants/tsconfig.json +9 -0
  67. package/templates/default/packages/constants/tsup.config.ts +9 -0
  68. package/templates/default/packages/logger/package.json +27 -0
  69. package/templates/default/packages/logger/src/index.ts +197 -0
  70. package/templates/default/packages/logger/tsconfig.json +11 -0
  71. package/templates/default/packages/logger/tsup.config.ts +9 -0
  72. package/templates/default/packages/result/package.json +26 -0
  73. package/templates/default/packages/result/src/index.ts +142 -0
  74. package/templates/default/packages/result/tsconfig.json +9 -0
  75. package/templates/default/packages/result/tsup.config.ts +9 -0
  76. package/templates/default/packages/types/package.json +26 -0
  77. package/templates/default/packages/types/src/index.ts +78 -0
  78. package/templates/default/packages/types/tsconfig.json +9 -0
  79. package/templates/default/packages/types/tsup.config.ts +10 -0
  80. package/templates/default/packages/ui/package.json +38 -0
  81. package/templates/default/packages/ui/src/components/Button.tsx +58 -0
  82. package/templates/default/packages/ui/src/components/Card.tsx +85 -0
  83. package/templates/default/packages/ui/src/components/Input.tsx +45 -0
  84. package/templates/default/packages/ui/src/index.ts +15 -0
  85. package/templates/default/packages/ui/tsconfig.json +11 -0
  86. package/templates/default/packages/ui/tsup.config.ts +11 -0
  87. package/templates/default/packages/utils/package.json +30 -0
  88. package/templates/default/packages/utils/src/index.test.ts +130 -0
  89. package/templates/default/packages/utils/src/index.ts +154 -0
  90. package/templates/default/packages/utils/tsconfig.json +10 -0
  91. package/templates/default/packages/utils/tsup.config.ts +10 -0
  92. package/templates/default/pnpm-workspace.yaml +3 -0
  93. package/templates/default/scripts/audit.mjs +700 -0
  94. package/templates/default/scripts/deploy.mjs +40 -0
  95. package/templates/default/scripts/generate-app.mjs +808 -0
  96. package/templates/default/scripts/lib/package-manager.mjs +186 -0
  97. package/templates/default/scripts/setup.mjs +102 -0
  98. package/templates/default/services/.env.example +16 -0
  99. package/templates/default/services/docker-compose.yml +207 -0
  100. package/templates/default/services/grafana/provisioning/dashboards/dashboards.yml +11 -0
  101. package/templates/default/services/grafana/provisioning/datasources/datasources.yml +9 -0
  102. package/templates/default/services/postgres/init/gitkeep +2 -0
  103. package/templates/default/services/prometheus/prometheus.yml +13 -0
  104. package/templates/default/tsconfig.json +27 -0
  105. package/templates/default/turbo.json +40 -0
  106. package/templates/default/vitest.config.ts +15 -0
@@ -0,0 +1,700 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Code Audit Script
5
+ *
6
+ * Runs multiple security and code quality checks on the monorepo:
7
+ * - npm audit: Check for vulnerable dependencies
8
+ * - ESLint: Code quality and potential bugs
9
+ * - TypeScript: Type checking
10
+ * - Secrets detection: Check for hardcoded secrets
11
+ * - License check: Verify dependency licenses
12
+ * - Dependency check: Outdated and unused dependencies
13
+ */
14
+
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { fileURLToPath } from 'url';
18
+ import { execSync, spawnSync } from 'child_process';
19
+ import { detectPackageManager, getRunCommand, getExecCommand } from './lib/package-manager.mjs';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+
24
+ // Colors for console output
25
+ const colors = {
26
+ red: '\x1b[31m',
27
+ green: '\x1b[32m',
28
+ blue: '\x1b[34m',
29
+ yellow: '\x1b[33m',
30
+ cyan: '\x1b[36m',
31
+ magenta: '\x1b[35m',
32
+ bold: '\x1b[1m',
33
+ dim: '\x1b[2m',
34
+ reset: '\x1b[0m',
35
+ };
36
+
37
+ const log = {
38
+ info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
39
+ success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
40
+ warn: (msg) => console.log(`${colors.yellow}!${colors.reset} ${msg}`),
41
+ error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
42
+ title: (msg) => console.log(`\n${colors.bold}${colors.cyan}${msg}${colors.reset}\n`),
43
+ section: (msg) => console.log(`\n${colors.bold}${colors.magenta}▶ ${msg}${colors.reset}\n`),
44
+ dim: (msg) => console.log(`${colors.dim}${msg}${colors.reset}`),
45
+ };
46
+
47
+ // Get directories
48
+ const ROOT_DIR = path.resolve(__dirname, '..');
49
+ const APPS_DIR = path.join(ROOT_DIR, 'apps');
50
+ const PACKAGES_DIR = path.join(ROOT_DIR, 'packages');
51
+
52
+ // Detect package manager
53
+ const pm = detectPackageManager(ROOT_DIR);
54
+ const runCmd = getRunCommand(pm);
55
+ const execCmd = getExecCommand(pm);
56
+
57
+ // Parse arguments
58
+ const args = process.argv.slice(2);
59
+ const options = {
60
+ fix: args.includes('--fix'),
61
+ verbose: args.includes('--verbose') || args.includes('-v'),
62
+ security: args.includes('--security') || args.includes('-s') || args.length === 0,
63
+ quality: args.includes('--quality') || args.includes('-q') || args.length === 0,
64
+ deps: args.includes('--deps') || args.includes('-d') || args.length === 0,
65
+ secrets: args.includes('--secrets') || args.length === 0,
66
+ all: args.includes('--all') || args.includes('-a'),
67
+ app: args.find((a) => a.startsWith('--app='))?.split('=')[1],
68
+ help: args.includes('--help') || args.includes('-h'),
69
+ };
70
+
71
+ if (options.all) {
72
+ options.security = true;
73
+ options.quality = true;
74
+ options.deps = true;
75
+ options.secrets = true;
76
+ }
77
+
78
+ // Results tracking
79
+ const results = {
80
+ passed: 0,
81
+ warnings: 0,
82
+ errors: 0,
83
+ checks: [],
84
+ };
85
+
86
+ function addResult(name, status, message = '') {
87
+ results.checks.push({ name, status, message });
88
+ if (status === 'pass') results.passed++;
89
+ else if (status === 'warn') results.warnings++;
90
+ else if (status === 'error') results.errors++;
91
+ }
92
+
93
+ // Run command and return result
94
+ function runCommand(cmd, cwd = ROOT_DIR, silent = false) {
95
+ try {
96
+ const output = execSync(cmd, {
97
+ cwd,
98
+ encoding: 'utf-8',
99
+ stdio: silent ? 'pipe' : ['pipe', 'pipe', 'pipe'],
100
+ });
101
+ return { success: true, output };
102
+ } catch (error) {
103
+ return {
104
+ success: false,
105
+ output: error.stdout || '',
106
+ error: error.stderr || error.message,
107
+ exitCode: error.status,
108
+ };
109
+ }
110
+ }
111
+
112
+ // Check if command exists
113
+ function commandExists(cmd) {
114
+ const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
115
+ stdio: 'pipe',
116
+ });
117
+ return result.status === 0;
118
+ }
119
+
120
+ // Get list of apps
121
+ function getApps() {
122
+ if (!fs.existsSync(APPS_DIR)) return [];
123
+ return fs
124
+ .readdirSync(APPS_DIR, { withFileTypes: true })
125
+ .filter((d) => d.isDirectory())
126
+ .map((d) => d.name);
127
+ }
128
+
129
+ // Get list of packages
130
+ function getPackages() {
131
+ if (!fs.existsSync(PACKAGES_DIR)) return [];
132
+ return fs
133
+ .readdirSync(PACKAGES_DIR, { withFileTypes: true })
134
+ .filter((d) => d.isDirectory())
135
+ .map((d) => d.name);
136
+ }
137
+
138
+ // ============================================================
139
+ // Security Checks
140
+ // ============================================================
141
+
142
+ async function checkDependencyVulnerabilities() {
143
+ log.section('Dependency Vulnerabilities');
144
+
145
+ let cmd;
146
+ if (pm === 'pnpm') {
147
+ cmd = 'pnpm audit --json';
148
+ } else if (pm === 'yarn') {
149
+ cmd = 'yarn audit --json';
150
+ } else {
151
+ cmd = 'npm audit --json';
152
+ }
153
+
154
+ const result = runCommand(cmd, ROOT_DIR, true);
155
+
156
+ try {
157
+ // Parse audit results
158
+ let vulnerabilities = { critical: 0, high: 0, moderate: 0, low: 0 };
159
+
160
+ if (pm === 'pnpm') {
161
+ // pnpm audit format
162
+ const lines = result.output.split('\n').filter((l) => l.trim());
163
+ for (const line of lines) {
164
+ try {
165
+ const data = JSON.parse(line);
166
+ if (data.type === 'auditSummary') {
167
+ vulnerabilities = data.data.vulnerabilities || vulnerabilities;
168
+ }
169
+ } catch {
170
+ // Skip non-JSON lines
171
+ }
172
+ }
173
+ } else {
174
+ // npm/yarn audit format
175
+ const data = JSON.parse(result.output || '{}');
176
+ vulnerabilities = data.metadata?.vulnerabilities || vulnerabilities;
177
+ }
178
+
179
+ const total = vulnerabilities.critical + vulnerabilities.high + vulnerabilities.moderate + vulnerabilities.low;
180
+
181
+ if (vulnerabilities.critical > 0 || vulnerabilities.high > 0) {
182
+ log.error(`Found ${vulnerabilities.critical} critical and ${vulnerabilities.high} high vulnerabilities`);
183
+ addResult('Dependency Vulnerabilities', 'error', `${vulnerabilities.critical} critical, ${vulnerabilities.high} high`);
184
+ } else if (vulnerabilities.moderate > 0 || vulnerabilities.low > 0) {
185
+ log.warn(`Found ${vulnerabilities.moderate} moderate and ${vulnerabilities.low} low vulnerabilities`);
186
+ addResult('Dependency Vulnerabilities', 'warn', `${vulnerabilities.moderate} moderate, ${vulnerabilities.low} low`);
187
+ } else {
188
+ log.success('No vulnerabilities found');
189
+ addResult('Dependency Vulnerabilities', 'pass');
190
+ }
191
+
192
+ if (options.verbose && total > 0) {
193
+ log.dim(`Run '${pm} audit' for details`);
194
+ }
195
+ } catch {
196
+ if (result.exitCode === 0) {
197
+ log.success('No vulnerabilities found');
198
+ addResult('Dependency Vulnerabilities', 'pass');
199
+ } else {
200
+ log.warn('Could not parse audit results');
201
+ addResult('Dependency Vulnerabilities', 'warn', 'Could not parse results');
202
+ }
203
+ }
204
+ }
205
+
206
+ // ============================================================
207
+ // Secrets Detection
208
+ // ============================================================
209
+
210
+ const SECRET_PATTERNS = [
211
+ { name: 'AWS Access Key', pattern: /AKIA[0-9A-Z]{16}/g },
212
+ { name: 'AWS Secret Key', pattern: /[0-9a-zA-Z/+]{40}/g, context: /aws|secret/i },
213
+ { name: 'GitHub Token', pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/g },
214
+ { name: 'Generic API Key', pattern: /api[_-]?key['":\s]*['"]?[A-Za-z0-9_\-]{20,}['"]?/gi },
215
+ { name: 'Generic Secret', pattern: /secret['":\s]*['"]?[A-Za-z0-9_\-]{20,}['"]?/gi },
216
+ { name: 'Private Key', pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
217
+ { name: 'JWT Token', pattern: /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/g },
218
+ { name: 'Basic Auth', pattern: /basic\s+[A-Za-z0-9+/=]{20,}/gi },
219
+ { name: 'Bearer Token', pattern: /bearer\s+[A-Za-z0-9_\-.~+/]+=*/gi },
220
+ { name: 'Database URL', pattern: /(?:postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@/gi },
221
+ { name: 'Password in URL', pattern: /(?:password|passwd|pwd)['"=:\s]+['"]?[^'"\s]{8,}['"]?/gi },
222
+ ];
223
+
224
+ const IGNORE_PATHS = [
225
+ 'node_modules',
226
+ '.git',
227
+ 'dist',
228
+ 'build',
229
+ '.next',
230
+ '.nuxt',
231
+ 'coverage',
232
+ '.turbo',
233
+ '*.lock',
234
+ 'pnpm-lock.yaml',
235
+ 'package-lock.json',
236
+ 'yarn.lock',
237
+ ];
238
+
239
+ const IGNORE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
240
+
241
+ function shouldIgnorePath(filePath) {
242
+ const normalized = filePath.replace(/\\/g, '/');
243
+ return IGNORE_PATHS.some((ignore) => {
244
+ if (ignore.startsWith('*')) {
245
+ return normalized.endsWith(ignore.slice(1));
246
+ }
247
+ return normalized.includes(ignore);
248
+ });
249
+ }
250
+
251
+ function scanFileForSecrets(filePath) {
252
+ const ext = path.extname(filePath).toLowerCase();
253
+ if (IGNORE_EXTENSIONS.includes(ext)) return [];
254
+
255
+ try {
256
+ const content = fs.readFileSync(filePath, 'utf-8');
257
+ const findings = [];
258
+
259
+ for (const { name, pattern, context } of SECRET_PATTERNS) {
260
+ const matches = content.match(pattern);
261
+ if (matches) {
262
+ for (const match of matches) {
263
+ // Skip if context is required but not found
264
+ if (context && !context.test(content)) continue;
265
+
266
+ // Skip common false positives
267
+ if (match.includes('example') || match.includes('placeholder') || match.includes('your-')) continue;
268
+
269
+ // Skip if it's in a comment or documentation
270
+ const lineIndex = content.indexOf(match);
271
+ const lineStart = content.lastIndexOf('\n', lineIndex) + 1;
272
+ const line = content.slice(lineStart, content.indexOf('\n', lineIndex));
273
+
274
+ if (line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('*')) continue;
275
+
276
+ findings.push({
277
+ type: name,
278
+ file: filePath,
279
+ line: content.slice(0, lineIndex).split('\n').length,
280
+ preview: match.slice(0, 20) + '...',
281
+ });
282
+ }
283
+ }
284
+ }
285
+
286
+ return findings;
287
+ } catch {
288
+ return [];
289
+ }
290
+ }
291
+
292
+ function scanDirectoryForSecrets(dir, findings = []) {
293
+ if (!fs.existsSync(dir)) return findings;
294
+
295
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
296
+
297
+ for (const entry of entries) {
298
+ const fullPath = path.join(dir, entry.name);
299
+ const relativePath = path.relative(ROOT_DIR, fullPath);
300
+
301
+ if (shouldIgnorePath(relativePath)) continue;
302
+
303
+ if (entry.isDirectory()) {
304
+ scanDirectoryForSecrets(fullPath, findings);
305
+ } else if (entry.isFile()) {
306
+ findings.push(...scanFileForSecrets(fullPath));
307
+ }
308
+ }
309
+
310
+ return findings;
311
+ }
312
+
313
+ async function checkSecrets() {
314
+ log.section('Secrets Detection');
315
+
316
+ const scanDirs = [APPS_DIR, PACKAGES_DIR, path.join(ROOT_DIR, 'scripts')];
317
+ let allFindings = [];
318
+
319
+ for (const dir of scanDirs) {
320
+ if (fs.existsSync(dir)) {
321
+ allFindings.push(...scanDirectoryForSecrets(dir));
322
+ }
323
+ }
324
+
325
+ // Also scan root config files
326
+ const rootFiles = fs.readdirSync(ROOT_DIR).filter((f) => {
327
+ const ext = path.extname(f);
328
+ return ['.js', '.ts', '.json', '.yaml', '.yml', '.env'].includes(ext) && !f.includes('lock');
329
+ });
330
+
331
+ for (const file of rootFiles) {
332
+ allFindings.push(...scanFileForSecrets(path.join(ROOT_DIR, file)));
333
+ }
334
+
335
+ // Deduplicate findings
336
+ const uniqueFindings = allFindings.filter(
337
+ (f, i, arr) => arr.findIndex((x) => x.file === f.file && x.line === f.line && x.type === f.type) === i
338
+ );
339
+
340
+ if (uniqueFindings.length > 0) {
341
+ log.error(`Found ${uniqueFindings.length} potential secrets`);
342
+
343
+ if (options.verbose) {
344
+ for (const finding of uniqueFindings.slice(0, 10)) {
345
+ const relPath = path.relative(ROOT_DIR, finding.file);
346
+ log.dim(` ${finding.type}: ${relPath}:${finding.line}`);
347
+ }
348
+ if (uniqueFindings.length > 10) {
349
+ log.dim(` ... and ${uniqueFindings.length - 10} more`);
350
+ }
351
+ }
352
+
353
+ addResult('Secrets Detection', 'error', `${uniqueFindings.length} potential secrets found`);
354
+ } else {
355
+ log.success('No hardcoded secrets detected');
356
+ addResult('Secrets Detection', 'pass');
357
+ }
358
+ }
359
+
360
+ // ============================================================
361
+ // Code Quality Checks
362
+ // ============================================================
363
+
364
+ async function checkESLint() {
365
+ log.section('ESLint Analysis');
366
+
367
+ const eslintCmd = options.fix ? `${runCmd} lint --fix` : `${runCmd} lint`;
368
+
369
+ const result = runCommand(eslintCmd, ROOT_DIR, true);
370
+
371
+ if (result.success) {
372
+ log.success('No ESLint errors found');
373
+ addResult('ESLint', 'pass');
374
+ } else {
375
+ // Count errors and warnings from output
376
+ const errorMatch = result.output.match(/(\d+)\s+error/);
377
+ const warnMatch = result.output.match(/(\d+)\s+warning/);
378
+ const errors = errorMatch ? parseInt(errorMatch[1], 10) : 0;
379
+ const warnings = warnMatch ? parseInt(warnMatch[1], 10) : 0;
380
+
381
+ if (errors > 0) {
382
+ log.error(`Found ${errors} errors and ${warnings} warnings`);
383
+ addResult('ESLint', 'error', `${errors} errors, ${warnings} warnings`);
384
+ } else if (warnings > 0) {
385
+ log.warn(`Found ${warnings} warnings`);
386
+ addResult('ESLint', 'warn', `${warnings} warnings`);
387
+ } else {
388
+ log.success('No ESLint issues found');
389
+ addResult('ESLint', 'pass');
390
+ }
391
+
392
+ if (options.verbose && result.output) {
393
+ console.log(result.output.slice(0, 1000));
394
+ }
395
+ }
396
+ }
397
+
398
+ async function checkTypeScript() {
399
+ log.section('TypeScript Type Checking');
400
+
401
+ const result = runCommand(`${runCmd} typecheck`, ROOT_DIR, true);
402
+
403
+ if (result.success) {
404
+ log.success('No TypeScript errors found');
405
+ addResult('TypeScript', 'pass');
406
+ } else {
407
+ // Count errors from output
408
+ const errorCount = (result.output.match(/error TS\d+/g) || []).length;
409
+
410
+ if (errorCount > 0) {
411
+ log.error(`Found ${errorCount} TypeScript errors`);
412
+ addResult('TypeScript', 'error', `${errorCount} errors`);
413
+
414
+ if (options.verbose && result.output) {
415
+ // Show first few errors
416
+ const lines = result.output.split('\n').slice(0, 20);
417
+ console.log(lines.join('\n'));
418
+ }
419
+ } else {
420
+ log.success('No TypeScript errors found');
421
+ addResult('TypeScript', 'pass');
422
+ }
423
+ }
424
+ }
425
+
426
+ // ============================================================
427
+ // Dependency Checks
428
+ // ============================================================
429
+
430
+ async function checkOutdatedDependencies() {
431
+ log.section('Outdated Dependencies');
432
+
433
+ let cmd;
434
+ if (pm === 'pnpm') {
435
+ cmd = 'pnpm outdated --format json';
436
+ } else if (pm === 'yarn') {
437
+ cmd = 'yarn outdated --json';
438
+ } else {
439
+ cmd = 'npm outdated --json';
440
+ }
441
+
442
+ const result = runCommand(cmd, ROOT_DIR, true);
443
+
444
+ try {
445
+ let outdatedCount = 0;
446
+ let majorUpdates = 0;
447
+
448
+ if (pm === 'pnpm') {
449
+ const lines = result.output.split('\n').filter((l) => l.trim());
450
+ for (const line of lines) {
451
+ try {
452
+ const data = JSON.parse(line);
453
+ if (Array.isArray(data)) {
454
+ outdatedCount = data.length;
455
+ majorUpdates = data.filter((d) => {
456
+ const current = d.current?.split('.')[0];
457
+ const latest = d.latest?.split('.')[0];
458
+ return current && latest && current !== latest;
459
+ }).length;
460
+ }
461
+ } catch {
462
+ // Skip non-JSON lines
463
+ }
464
+ }
465
+ } else {
466
+ const data = JSON.parse(result.output || '{}');
467
+ outdatedCount = Object.keys(data).length;
468
+ majorUpdates = Object.values(data).filter((d) => {
469
+ const current = d.current?.split('.')[0];
470
+ const latest = d.latest?.split('.')[0];
471
+ return current && latest && current !== latest;
472
+ }).length;
473
+ }
474
+
475
+ if (majorUpdates > 0) {
476
+ log.warn(`${outdatedCount} outdated packages (${majorUpdates} major updates available)`);
477
+ addResult('Outdated Dependencies', 'warn', `${outdatedCount} outdated, ${majorUpdates} major`);
478
+ } else if (outdatedCount > 0) {
479
+ log.info(`${outdatedCount} packages have minor/patch updates available`);
480
+ addResult('Outdated Dependencies', 'pass', `${outdatedCount} minor updates`);
481
+ } else {
482
+ log.success('All dependencies are up to date');
483
+ addResult('Outdated Dependencies', 'pass');
484
+ }
485
+
486
+ if (options.verbose) {
487
+ log.dim(`Run '${pm} outdated' for details`);
488
+ }
489
+ } catch {
490
+ log.success('All dependencies are up to date');
491
+ addResult('Outdated Dependencies', 'pass');
492
+ }
493
+ }
494
+
495
+ async function checkUnusedDependencies() {
496
+ log.section('Unused Dependencies');
497
+
498
+ // Check if depcheck is available
499
+ if (!commandExists('depcheck')) {
500
+ log.dim('depcheck not installed, skipping unused dependency check');
501
+ log.dim(`Install with: npm install -g depcheck`);
502
+ addResult('Unused Dependencies', 'warn', 'depcheck not installed');
503
+ return;
504
+ }
505
+
506
+ const apps = getApps();
507
+ const packages = getPackages();
508
+ let totalUnused = 0;
509
+
510
+ const checkDirs = [
511
+ ...apps.map((a) => ({ name: a, path: path.join(APPS_DIR, a) })),
512
+ ...packages.map((p) => ({ name: p, path: path.join(PACKAGES_DIR, p) })),
513
+ ];
514
+
515
+ for (const { name, path: dirPath } of checkDirs) {
516
+ if (!fs.existsSync(path.join(dirPath, 'package.json'))) continue;
517
+
518
+ const result = runCommand(`depcheck --json`, dirPath, true);
519
+
520
+ try {
521
+ const data = JSON.parse(result.output || '{}');
522
+ const unused = [...(data.dependencies || []), ...(data.devDependencies || [])];
523
+
524
+ if (unused.length > 0) {
525
+ totalUnused += unused.length;
526
+ if (options.verbose) {
527
+ log.dim(` ${name}: ${unused.join(', ')}`);
528
+ }
529
+ }
530
+ } catch {
531
+ // Skip parse errors
532
+ }
533
+ }
534
+
535
+ if (totalUnused > 0) {
536
+ log.warn(`Found ${totalUnused} potentially unused dependencies`);
537
+ addResult('Unused Dependencies', 'warn', `${totalUnused} unused`);
538
+ } else {
539
+ log.success('No unused dependencies detected');
540
+ addResult('Unused Dependencies', 'pass');
541
+ }
542
+ }
543
+
544
+ // ============================================================
545
+ // License Check
546
+ // ============================================================
547
+
548
+ async function checkLicenses() {
549
+ log.section('License Compliance');
550
+
551
+ // Check if license-checker is available
552
+ if (!commandExists('license-checker')) {
553
+ log.dim('license-checker not installed, skipping license check');
554
+ log.dim(`Install with: npm install -g license-checker`);
555
+ addResult('License Compliance', 'warn', 'license-checker not installed');
556
+ return;
557
+ }
558
+
559
+ const PROBLEMATIC_LICENSES = ['GPL', 'AGPL', 'LGPL', 'SSPL', 'BUSL', 'Commons Clause'];
560
+ const UNKNOWN_LICENSE = 'UNKNOWN';
561
+
562
+ const result = runCommand('license-checker --json --production', ROOT_DIR, true);
563
+
564
+ try {
565
+ const data = JSON.parse(result.output || '{}');
566
+ const packages = Object.entries(data);
567
+ const problematic = [];
568
+ const unknown = [];
569
+
570
+ for (const [pkg, info] of packages) {
571
+ const license = info.licenses || UNKNOWN_LICENSE;
572
+ const licenseStr = Array.isArray(license) ? license.join(', ') : license;
573
+
574
+ if (licenseStr === UNKNOWN_LICENSE) {
575
+ unknown.push(pkg);
576
+ } else if (PROBLEMATIC_LICENSES.some((l) => licenseStr.toUpperCase().includes(l))) {
577
+ problematic.push({ pkg, license: licenseStr });
578
+ }
579
+ }
580
+
581
+ if (problematic.length > 0) {
582
+ log.warn(`Found ${problematic.length} packages with restrictive licenses`);
583
+ if (options.verbose) {
584
+ for (const { pkg, license } of problematic.slice(0, 5)) {
585
+ log.dim(` ${pkg}: ${license}`);
586
+ }
587
+ }
588
+ addResult('License Compliance', 'warn', `${problematic.length} restrictive licenses`);
589
+ } else if (unknown.length > 5) {
590
+ log.warn(`Found ${unknown.length} packages with unknown licenses`);
591
+ addResult('License Compliance', 'warn', `${unknown.length} unknown licenses`);
592
+ } else {
593
+ log.success('All licenses are compliant');
594
+ addResult('License Compliance', 'pass');
595
+ }
596
+ } catch {
597
+ log.warn('Could not check licenses');
598
+ addResult('License Compliance', 'warn', 'Check failed');
599
+ }
600
+ }
601
+
602
+ // ============================================================
603
+ // Main
604
+ // ============================================================
605
+
606
+ function showHelp() {
607
+ console.log(`
608
+ ${colors.bold}Usage:${colors.reset} pnpm audit [options]
609
+
610
+ ${colors.bold}Options:${colors.reset}
611
+ -a, --all Run all checks
612
+ -s, --security Run security checks only (vulnerabilities, secrets)
613
+ -q, --quality Run code quality checks only (ESLint, TypeScript)
614
+ -d, --deps Run dependency checks only (outdated, unused, licenses)
615
+ --secrets Run secrets detection only
616
+ --fix Attempt to fix issues (ESLint)
617
+ -v, --verbose Show detailed output
618
+ --app=<name> Audit specific app only
619
+ -h, --help Show this help message
620
+
621
+ ${colors.bold}Examples:${colors.reset}
622
+ pnpm audit # Run all checks
623
+ pnpm audit --security # Security checks only
624
+ pnpm audit --quality --fix # Code quality with auto-fix
625
+ pnpm audit --app=web # Audit specific app
626
+ `);
627
+ }
628
+
629
+ function printSummary() {
630
+ log.title('📊 Audit Summary');
631
+
632
+ const statusIcon = {
633
+ pass: `${colors.green}✓${colors.reset}`,
634
+ warn: `${colors.yellow}!${colors.reset}`,
635
+ error: `${colors.red}✗${colors.reset}`,
636
+ };
637
+
638
+ for (const check of results.checks) {
639
+ const icon = statusIcon[check.status];
640
+ const msg = check.message ? ` (${check.message})` : '';
641
+ console.log(` ${icon} ${check.name}${colors.dim}${msg}${colors.reset}`);
642
+ }
643
+
644
+ console.log('');
645
+ console.log(
646
+ `${colors.bold}Results:${colors.reset} ` +
647
+ `${colors.green}${results.passed} passed${colors.reset}, ` +
648
+ `${colors.yellow}${results.warnings} warnings${colors.reset}, ` +
649
+ `${colors.red}${results.errors} errors${colors.reset}`
650
+ );
651
+ console.log('');
652
+
653
+ if (results.errors > 0) {
654
+ process.exit(1);
655
+ }
656
+ }
657
+
658
+ async function main() {
659
+ if (options.help) {
660
+ showHelp();
661
+ process.exit(0);
662
+ }
663
+
664
+ log.title('🔍 Code Audit');
665
+ log.info(`Package manager: ${pm}`);
666
+ log.info(`Root directory: ${ROOT_DIR}`);
667
+
668
+ if (options.app) {
669
+ log.info(`Auditing app: ${options.app}`);
670
+ }
671
+
672
+ // Security checks
673
+ if (options.security) {
674
+ await checkDependencyVulnerabilities();
675
+ }
676
+
677
+ if (options.secrets) {
678
+ await checkSecrets();
679
+ }
680
+
681
+ // Code quality checks
682
+ if (options.quality) {
683
+ await checkESLint();
684
+ await checkTypeScript();
685
+ }
686
+
687
+ // Dependency checks
688
+ if (options.deps) {
689
+ await checkOutdatedDependencies();
690
+ await checkUnusedDependencies();
691
+ await checkLicenses();
692
+ }
693
+
694
+ printSummary();
695
+ }
696
+
697
+ main().catch((error) => {
698
+ log.error(error.message);
699
+ process.exit(1);
700
+ });