kaddidlehopper 0.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.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, readdirSync, statSync, writeFileSync } from 'fs'
4
+ import { join, dirname, relative } from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import semver from 'semver'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+
11
+ // Parse command line arguments
12
+ const args = process.argv.slice(2)
13
+ const shouldUpdate = args.includes('--update')
14
+
15
+ // Colors for console output
16
+ const colors = {
17
+ red: '\x1b[31m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m',
21
+ reset: '\x1b[0m',
22
+ bold: '\x1b[1m',
23
+ }
24
+
25
+ /**
26
+ * Recursively find all package.json files in a directory
27
+ */
28
+ function findPackageJsonFiles(dir, files = []) {
29
+ const items = readdirSync(dir)
30
+
31
+ for (const item of items) {
32
+ const fullPath = join(dir, item)
33
+ const stat = statSync(fullPath)
34
+
35
+ if (stat.isDirectory() && item !== 'node_modules' && item !== 'dist') {
36
+ findPackageJsonFiles(fullPath, files)
37
+ } else if (item === 'package.json') {
38
+ files.push(fullPath)
39
+ }
40
+ }
41
+
42
+ return files
43
+ }
44
+
45
+ // Cache for package versions to avoid duplicate API calls
46
+ const versionCache = new Map()
47
+
48
+ /**
49
+ * Get the latest version of a package from npm with caching and timeout
50
+ */
51
+ async function getLatestVersion(packageName) {
52
+ // Check cache first
53
+ if (versionCache.has(packageName)) {
54
+ return versionCache.get(packageName)
55
+ }
56
+
57
+ try {
58
+ // Add timeout to prevent hanging
59
+ const controller = new AbortController()
60
+ const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
61
+
62
+ const response = await fetch(
63
+ `https://registry.npmjs.org/${packageName}/latest`,
64
+ {
65
+ signal: controller.signal,
66
+ headers: {
67
+ 'User-Agent': 'netlify-cta-outdated-checker',
68
+ },
69
+ },
70
+ )
71
+
72
+ clearTimeout(timeoutId)
73
+
74
+ if (!response.ok) {
75
+ throw new Error(`HTTP ${response.status}`)
76
+ }
77
+
78
+ const data = await response.json()
79
+ const version = data.version
80
+
81
+ // Cache the result
82
+ versionCache.set(packageName, version)
83
+ return version
84
+ } catch (error) {
85
+ if (error.name === 'AbortError') {
86
+ console.warn(
87
+ `${colors.yellow}Warning: Timeout fetching latest version for ${packageName}${colors.reset}`,
88
+ )
89
+ } else {
90
+ console.warn(
91
+ `${colors.yellow}Warning: Could not fetch latest version for ${packageName}: ${error.message}${colors.reset}`,
92
+ )
93
+ }
94
+
95
+ // Cache null result to avoid retrying
96
+ versionCache.set(packageName, null)
97
+ return null
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Check if a version range satisfies the latest version
103
+ */
104
+ function isVersionRangeOutdated(versionRange, latestVersion) {
105
+ try {
106
+ // Clean the version range (remove any npm-specific prefixes)
107
+ const cleanRange = versionRange.replace(/^npm:/, '').split('@').pop()
108
+
109
+ // Check if the range satisfies the latest version
110
+ return !semver.satisfies(latestVersion, cleanRange)
111
+ } catch (error) {
112
+ console.warn(
113
+ `${colors.yellow}Warning: Could not parse version range "${versionRange}": ${error.message}${colors.reset}`,
114
+ )
115
+ return false
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Update package.json file with new versions
121
+ */
122
+ function updatePackageJson(filePath, outdatedPackages) {
123
+ try {
124
+ const content = readFileSync(filePath, 'utf8')
125
+ const packageJson = JSON.parse(content)
126
+
127
+ let updated = false
128
+
129
+ for (const pkg of outdatedPackages) {
130
+ // Check and update in dependencies
131
+ if (packageJson.dependencies && packageJson.dependencies[pkg.name]) {
132
+ const currentRange = packageJson.dependencies[pkg.name]
133
+ const newRange = updateVersionRange(currentRange, pkg.latest)
134
+ packageJson.dependencies[pkg.name] = newRange
135
+ updated = true
136
+ }
137
+
138
+ // Check and update in devDependencies
139
+ if (
140
+ packageJson.devDependencies &&
141
+ packageJson.devDependencies[pkg.name]
142
+ ) {
143
+ const currentRange = packageJson.devDependencies[pkg.name]
144
+ const newRange = updateVersionRange(currentRange, pkg.latest)
145
+ packageJson.devDependencies[pkg.name] = newRange
146
+ updated = true
147
+ }
148
+
149
+ // Check and update in peerDependencies
150
+ if (
151
+ packageJson.peerDependencies &&
152
+ packageJson.peerDependencies[pkg.name]
153
+ ) {
154
+ const currentRange = packageJson.peerDependencies[pkg.name]
155
+ const newRange = updateVersionRange(currentRange, pkg.latest)
156
+ packageJson.peerDependencies[pkg.name] = newRange
157
+ updated = true
158
+ }
159
+ }
160
+
161
+ if (updated) {
162
+ // Write back with proper formatting
163
+ writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n')
164
+ return true
165
+ }
166
+
167
+ return false
168
+ } catch (error) {
169
+ console.error(
170
+ `${colors.red}Error updating ${filePath}: ${error.message}${colors.reset}`,
171
+ )
172
+ return false
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Update version range while preserving the range type (^, ~, etc.)
178
+ */
179
+ function updateVersionRange(currentRange, latestVersion) {
180
+ // Preserve the prefix (^, ~, >=, etc.)
181
+ if (currentRange.startsWith('^')) {
182
+ return `^${latestVersion}`
183
+ } else if (currentRange.startsWith('~')) {
184
+ return `~${latestVersion}`
185
+ } else if (currentRange.startsWith('>=')) {
186
+ return `>=${latestVersion}`
187
+ } else if (currentRange.startsWith('>')) {
188
+ return `>${latestVersion}`
189
+ } else if (currentRange.startsWith('<=')) {
190
+ return `<=${latestVersion}`
191
+ } else if (currentRange.startsWith('<')) {
192
+ return `<${latestVersion}`
193
+ } else {
194
+ // Exact version or other format, just use the latest version
195
+ return latestVersion
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Get relative path for display
201
+ */
202
+ function getDisplayPath(filePath, rootDir) {
203
+ const relativePath = relative(rootDir, filePath)
204
+ // Remove the package.json part
205
+ return relativePath.replace('/package.json', '')
206
+ }
207
+
208
+ /**
209
+ * Check dependencies in a package.json file
210
+ */
211
+ async function checkPackageJson(filePath) {
212
+ try {
213
+ const content = readFileSync(filePath, 'utf8')
214
+ const packageJson = JSON.parse(content)
215
+
216
+ const outdatedPackages = []
217
+ const allDependencies = {
218
+ ...packageJson.dependencies,
219
+ ...packageJson.devDependencies,
220
+ ...packageJson.peerDependencies,
221
+ }
222
+
223
+ if (Object.keys(allDependencies).length === 0) {
224
+ return { filePath, outdatedPackages: [], totalDependencies: 0 }
225
+ }
226
+
227
+ const packageNames = Object.keys(allDependencies).filter((name) => {
228
+ const versionRange = allDependencies[name]
229
+ // Skip workspace dependencies and local packages
230
+ return !(
231
+ versionRange.startsWith('workspace:') ||
232
+ versionRange.startsWith('file:') ||
233
+ versionRange.startsWith('link:')
234
+ )
235
+ })
236
+
237
+ // Process packages in batches to avoid overwhelming the API
238
+ const batchSize = 5
239
+ for (let i = 0; i < packageNames.length; i += batchSize) {
240
+ const batch = packageNames.slice(i, i + batchSize)
241
+
242
+ // Process batch in parallel
243
+ const promises = batch.map(async (packageName) => {
244
+ const versionRange = allDependencies[packageName]
245
+ const latestVersion = await getLatestVersion(packageName)
246
+
247
+ if (!latestVersion) {
248
+ return null
249
+ }
250
+
251
+ if (isVersionRangeOutdated(versionRange, latestVersion)) {
252
+ return {
253
+ name: packageName,
254
+ current: versionRange,
255
+ latest: latestVersion,
256
+ }
257
+ }
258
+
259
+ return null
260
+ })
261
+
262
+ const results = await Promise.all(promises)
263
+ outdatedPackages.push(...results.filter(Boolean))
264
+
265
+ // Small delay between batches
266
+ if (i + batchSize < packageNames.length) {
267
+ await new Promise((resolve) => setTimeout(resolve, 50))
268
+ }
269
+ }
270
+
271
+ return {
272
+ filePath,
273
+ outdatedPackages,
274
+ totalDependencies: Object.keys(allDependencies).length,
275
+ }
276
+ } catch (error) {
277
+ console.error(
278
+ `${colors.red}Error processing ${filePath}: ${error.message}${colors.reset}`,
279
+ )
280
+ return {
281
+ filePath,
282
+ outdatedPackages: [],
283
+ totalDependencies: 0,
284
+ error: error.message,
285
+ }
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Main function
291
+ */
292
+ async function main() {
293
+ const actionText = shouldUpdate ? 'Updating' : 'Checking for'
294
+ console.log(
295
+ `${colors.bold}${colors.blue}${actionText} outdated packages in netlify-cta...${colors.reset}\n`,
296
+ )
297
+
298
+ const rootDir = join(__dirname, '..')
299
+
300
+ // Directories to scan for package.json files
301
+ const dirsToScan = [
302
+ join(rootDir, 'add-ons'),
303
+ join(rootDir, 'examples'),
304
+ join(rootDir, 'project'),
305
+ ]
306
+
307
+ // Collect all package.json files from all directories
308
+ const packageJsonFiles = []
309
+ for (const dir of dirsToScan) {
310
+ try {
311
+ const files = findPackageJsonFiles(dir)
312
+ packageJsonFiles.push(...files)
313
+ console.log(
314
+ `${colors.blue}Found ${files.length} package.json files in ${relative(rootDir, dir)}${colors.reset}`,
315
+ )
316
+ } catch (error) {
317
+ console.warn(
318
+ `${colors.yellow}Warning: Could not scan ${dir}: ${error.message}${colors.reset}`,
319
+ )
320
+ }
321
+ }
322
+
323
+ console.log(`\nTotal: ${packageJsonFiles.length} package.json files\n`)
324
+
325
+ let totalOutdated = 0
326
+ let totalChecked = 0
327
+ let totalUpdated = 0
328
+ const results = []
329
+
330
+ for (let i = 0; i < packageJsonFiles.length; i++) {
331
+ const filePath = packageJsonFiles[i]
332
+ const displayPath = getDisplayPath(filePath, rootDir)
333
+
334
+ // Show progress
335
+ process.stdout.write(
336
+ `\r${colors.blue}Processing ${i + 1}/${packageJsonFiles.length}: ${displayPath}...${colors.reset}`,
337
+ )
338
+
339
+ const result = await checkPackageJson(filePath)
340
+ results.push(result)
341
+
342
+ if (result.error) {
343
+ console.log(`\n${colors.red}Error: ${result.error}${colors.reset}`)
344
+ continue
345
+ }
346
+
347
+ totalChecked += result.totalDependencies
348
+ totalOutdated += result.outdatedPackages.length
349
+
350
+ if (result.outdatedPackages.length > 0) {
351
+ console.log(
352
+ `\n${colors.red}${colors.bold}Found ${result.outdatedPackages.length} outdated packages in ${displayPath}:${colors.reset}`,
353
+ )
354
+ for (const pkg of result.outdatedPackages) {
355
+ console.log(
356
+ ` ${colors.red}${pkg.name}${colors.reset}: ${pkg.current} → ${colors.green}${pkg.latest}${colors.reset}`,
357
+ )
358
+ }
359
+
360
+ if (shouldUpdate) {
361
+ const updated = updatePackageJson(filePath, result.outdatedPackages)
362
+ if (updated) {
363
+ totalUpdated++
364
+ console.log(
365
+ ` ${colors.green}✓ Updated ${displayPath}${colors.reset}`,
366
+ )
367
+ } else {
368
+ console.log(
369
+ ` ${colors.yellow}⚠ Failed to update ${displayPath}${colors.reset}`,
370
+ )
371
+ }
372
+ }
373
+
374
+ console.log()
375
+ }
376
+ }
377
+
378
+ // Clear progress line and add spacing
379
+ console.log('\n')
380
+
381
+ // Summary
382
+ console.log(`${colors.bold}${colors.blue}Summary:${colors.reset}`)
383
+ console.log(`Total dependencies checked: ${totalChecked}`)
384
+ console.log(
385
+ `Total outdated packages: ${colors.red}${totalOutdated}${colors.reset}`,
386
+ )
387
+
388
+ if (shouldUpdate) {
389
+ console.log(
390
+ `Total files updated: ${colors.green}${totalUpdated}${colors.reset}`,
391
+ )
392
+ }
393
+
394
+ if (totalOutdated === 0) {
395
+ console.log(
396
+ `${colors.green}${colors.bold}🎉 All packages are up to date!${colors.reset}`,
397
+ )
398
+ } else if (shouldUpdate && totalUpdated > 0) {
399
+ console.log(
400
+ `${colors.green}${colors.bold}✅ Successfully updated ${totalUpdated} files with outdated packages!${colors.reset}`,
401
+ )
402
+ } else if (shouldUpdate && totalUpdated === 0) {
403
+ console.log(
404
+ `${colors.yellow}${colors.bold}⚠️ No files were updated. Check for errors above.${colors.reset}`,
405
+ )
406
+ } else {
407
+ console.log(
408
+ `${colors.yellow}${colors.bold}⚠️ Found outdated packages that need attention. Use --update to update them.${colors.reset}`,
409
+ )
410
+ }
411
+
412
+ // Exit with error code if outdated packages found and not updating
413
+ process.exit(totalOutdated > 0 && !shouldUpdate ? 1 : 0)
414
+ }
415
+
416
+ main().catch((error) => {
417
+ console.error(
418
+ `${colors.red}${colors.bold}Fatal error: ${error.message}${colors.reset}`,
419
+ )
420
+ process.exit(1)
421
+ })