@buoy-design/cli 0.1.0 → 0.1.2

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 (146) hide show
  1. package/dist/commands/audit.d.ts +3 -0
  2. package/dist/commands/audit.d.ts.map +1 -0
  3. package/dist/commands/audit.js +235 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/baseline.d.ts +39 -0
  6. package/dist/commands/baseline.d.ts.map +1 -0
  7. package/dist/commands/baseline.js +298 -0
  8. package/dist/commands/baseline.js.map +1 -0
  9. package/dist/commands/check.d.ts +16 -0
  10. package/dist/commands/check.d.ts.map +1 -0
  11. package/dist/commands/check.js +168 -0
  12. package/dist/commands/check.js.map +1 -0
  13. package/dist/commands/ci.d.ts +2 -2
  14. package/dist/commands/ci.d.ts.map +1 -1
  15. package/dist/commands/ci.js +124 -142
  16. package/dist/commands/ci.js.map +1 -1
  17. package/dist/commands/compare.d.ts +3 -0
  18. package/dist/commands/compare.d.ts.map +1 -0
  19. package/dist/commands/compare.js +170 -0
  20. package/dist/commands/compare.js.map +1 -0
  21. package/dist/commands/drift.d.ts +1 -1
  22. package/dist/commands/drift.d.ts.map +1 -1
  23. package/dist/commands/drift.js +79 -121
  24. package/dist/commands/drift.js.map +1 -1
  25. package/dist/commands/explain.d.ts +3 -0
  26. package/dist/commands/explain.d.ts.map +1 -0
  27. package/dist/commands/explain.js +212 -0
  28. package/dist/commands/explain.js.map +1 -0
  29. package/dist/commands/graph.d.ts +3 -0
  30. package/dist/commands/graph.d.ts.map +1 -0
  31. package/dist/commands/graph.js +430 -0
  32. package/dist/commands/graph.js.map +1 -0
  33. package/dist/commands/index.d.ts +14 -8
  34. package/dist/commands/index.d.ts.map +1 -1
  35. package/dist/commands/index.js +14 -8
  36. package/dist/commands/index.js.map +1 -1
  37. package/dist/commands/init.d.ts +1 -1
  38. package/dist/commands/init.d.ts.map +1 -1
  39. package/dist/commands/init.js +404 -237
  40. package/dist/commands/init.js.map +1 -1
  41. package/dist/commands/plugins.d.ts.map +1 -1
  42. package/dist/commands/plugins.js +32 -41
  43. package/dist/commands/plugins.js.map +1 -1
  44. package/dist/commands/scan.d.ts +1 -1
  45. package/dist/commands/scan.d.ts.map +1 -1
  46. package/dist/commands/scan.js +107 -219
  47. package/dist/commands/scan.js.map +1 -1
  48. package/dist/commands/status.d.ts +1 -1
  49. package/dist/commands/status.d.ts.map +1 -1
  50. package/dist/commands/status.js +114 -122
  51. package/dist/commands/status.js.map +1 -1
  52. package/dist/commands/tokens.d.ts +3 -0
  53. package/dist/commands/tokens.d.ts.map +1 -0
  54. package/dist/commands/tokens.js +261 -0
  55. package/dist/commands/tokens.js.map +1 -0
  56. package/dist/config/auto-detect.d.ts +21 -0
  57. package/dist/config/auto-detect.d.ts.map +1 -0
  58. package/dist/config/auto-detect.js +278 -0
  59. package/dist/config/auto-detect.js.map +1 -0
  60. package/dist/config/loader.d.ts.map +1 -1
  61. package/dist/config/loader.js +17 -0
  62. package/dist/config/loader.js.map +1 -1
  63. package/dist/config/schema.d.ts +63 -63
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +20 -2
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/constants.d.ts +36 -0
  68. package/dist/constants.d.ts.map +1 -0
  69. package/dist/constants.js +37 -0
  70. package/dist/constants.js.map +1 -0
  71. package/dist/detect/frameworks.d.ts +11 -2
  72. package/dist/detect/frameworks.d.ts.map +1 -1
  73. package/dist/detect/frameworks.js +78 -78
  74. package/dist/detect/frameworks.js.map +1 -1
  75. package/dist/detect/index.d.ts +1 -0
  76. package/dist/detect/index.d.ts.map +1 -1
  77. package/dist/detect/index.js +3 -0
  78. package/dist/detect/index.js.map +1 -1
  79. package/dist/detect/monorepo-patterns.d.ts +54 -0
  80. package/dist/detect/monorepo-patterns.d.ts.map +1 -0
  81. package/dist/detect/monorepo-patterns.js +209 -0
  82. package/dist/detect/monorepo-patterns.js.map +1 -0
  83. package/dist/detect/project-detector.d.ts +1 -1
  84. package/dist/detect/project-detector.d.ts.map +1 -1
  85. package/dist/detect/project-detector.js +132 -0
  86. package/dist/detect/project-detector.js.map +1 -1
  87. package/dist/explain/agents.d.ts +31 -0
  88. package/dist/explain/agents.d.ts.map +1 -0
  89. package/dist/explain/agents.js +507 -0
  90. package/dist/explain/agents.js.map +1 -0
  91. package/dist/hooks/index.d.ts +26 -0
  92. package/dist/hooks/index.d.ts.map +1 -0
  93. package/dist/hooks/index.js +283 -0
  94. package/dist/hooks/index.js.map +1 -0
  95. package/dist/index.d.ts +3 -3
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +13 -7
  98. package/dist/index.js.map +1 -1
  99. package/dist/integrations/github-formatter.d.ts +55 -0
  100. package/dist/integrations/github-formatter.d.ts.map +1 -0
  101. package/dist/integrations/github-formatter.js +391 -0
  102. package/dist/integrations/github-formatter.js.map +1 -0
  103. package/dist/integrations/github.d.ts +95 -0
  104. package/dist/integrations/github.d.ts.map +1 -0
  105. package/dist/integrations/github.js +281 -0
  106. package/dist/integrations/github.js.map +1 -0
  107. package/dist/integrations/index.d.ts +4 -0
  108. package/dist/integrations/index.d.ts.map +1 -0
  109. package/dist/integrations/index.js +4 -0
  110. package/dist/integrations/index.js.map +1 -0
  111. package/dist/output/formatters.d.ts.map +1 -1
  112. package/dist/output/formatters.js +5 -10
  113. package/dist/output/formatters.js.map +1 -1
  114. package/dist/scan/orchestrator.d.ts +76 -0
  115. package/dist/scan/orchestrator.d.ts.map +1 -0
  116. package/dist/scan/orchestrator.js +247 -0
  117. package/dist/scan/orchestrator.js.map +1 -0
  118. package/dist/services/ai-analysis.d.ts +54 -0
  119. package/dist/services/ai-analysis.d.ts.map +1 -0
  120. package/dist/services/ai-analysis.js +239 -0
  121. package/dist/services/ai-analysis.js.map +1 -0
  122. package/dist/services/drift-analysis.d.ts +77 -0
  123. package/dist/services/drift-analysis.d.ts.map +1 -0
  124. package/dist/services/drift-analysis.js +145 -0
  125. package/dist/services/drift-analysis.js.map +1 -0
  126. package/package.json +17 -5
  127. package/dist/commands/__tests__/ci.test.d.ts +0 -2
  128. package/dist/commands/__tests__/ci.test.d.ts.map +0 -1
  129. package/dist/commands/__tests__/ci.test.js +0 -33
  130. package/dist/commands/__tests__/ci.test.js.map +0 -1
  131. package/dist/commands/bootstrap.d.ts +0 -3
  132. package/dist/commands/bootstrap.d.ts.map +0 -1
  133. package/dist/commands/bootstrap.js +0 -458
  134. package/dist/commands/bootstrap.js.map +0 -1
  135. package/dist/plugins/index.d.ts +0 -3
  136. package/dist/plugins/index.d.ts.map +0 -1
  137. package/dist/plugins/index.js +0 -3
  138. package/dist/plugins/index.js.map +0 -1
  139. package/dist/plugins/loader.d.ts +0 -11
  140. package/dist/plugins/loader.d.ts.map +0 -1
  141. package/dist/plugins/loader.js +0 -77
  142. package/dist/plugins/loader.js.map +0 -1
  143. package/dist/plugins/registry.d.ts +0 -15
  144. package/dist/plugins/registry.d.ts.map +0 -1
  145. package/dist/plugins/registry.js +0 -32
  146. package/dist/plugins/registry.js.map +0 -1
@@ -1,15 +1,17 @@
1
- import { Command } from 'commander';
2
- import { writeFileSync, existsSync } from 'fs';
3
- import { resolve } from 'path';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { createInterface } from 'readline';
7
- import { success, error, info, warning } from '../output/reporters.js';
8
- import { ProjectDetector } from '../detect/index.js';
9
- import { detectFrameworks, getPluginInstallCommand, PLUGIN_INFO } from '../detect/frameworks.js';
10
- import { discoverPlugins } from '../plugins/index.js';
1
+ import { Command } from "commander";
2
+ import { writeFileSync, existsSync } from "fs";
3
+ import { resolve } from "path";
4
+ import chalk from "chalk";
5
+ import ora from "ora";
6
+ import { createInterface } from "readline";
7
+ import { success, error, info, warning } from "../output/reporters.js";
8
+ import { ProjectDetector, detectMonorepoConfig, expandPatternsForMonorepo, } from "../detect/index.js";
9
+ import { detectFrameworks, getPluginInstallCommand, PLUGIN_INFO, BUILTIN_SCANNERS, } from "../detect/frameworks.js";
10
+ import { setupHooks, generateStandaloneHook, detectHookSystem, } from "../hooks/index.js";
11
11
  function generateConfig(project) {
12
12
  const lines = [];
13
+ // Detect monorepo configuration for pattern expansion
14
+ const monorepoConfig = detectMonorepoConfig(project.root);
13
15
  lines.push(`/** @type {import('@buoy-design/cli').BuoyConfig} */`);
14
16
  lines.push(`export default {`);
15
17
  lines.push(` project: {`);
@@ -19,44 +21,53 @@ function generateConfig(project) {
19
21
  // Determine the correct source key based on framework
20
22
  const getSourceKey = (frameworkName) => {
21
23
  // React-based frameworks
22
- if (['react', 'nextjs', 'remix', 'gatsby', 'react-native', 'expo', 'preact', 'solid'].includes(frameworkName)) {
23
- return 'react';
24
+ if ([
25
+ "react",
26
+ "nextjs",
27
+ "remix",
28
+ "gatsby",
29
+ "react-native",
30
+ "expo",
31
+ "preact",
32
+ "solid",
33
+ ].includes(frameworkName)) {
34
+ return "react";
24
35
  }
25
36
  // Vue-based frameworks
26
- if (['vue', 'nuxt'].includes(frameworkName)) {
27
- return 'vue';
37
+ if (["vue", "nuxt"].includes(frameworkName)) {
38
+ return "vue";
28
39
  }
29
40
  // Svelte-based frameworks
30
- if (['svelte', 'sveltekit'].includes(frameworkName)) {
31
- return 'svelte';
41
+ if (["svelte", "sveltekit"].includes(frameworkName)) {
42
+ return "svelte";
32
43
  }
33
44
  // Angular
34
- if (frameworkName === 'angular') {
35
- return 'angular';
45
+ if (frameworkName === "angular") {
46
+ return "angular";
36
47
  }
37
48
  // Web Components
38
- if (['lit', 'stencil'].includes(frameworkName)) {
39
- return 'webcomponent';
49
+ if (["lit", "stencil"].includes(frameworkName)) {
50
+ return "webcomponent";
40
51
  }
41
52
  // Astro is special - can use multiple frameworks
42
- if (frameworkName === 'astro') {
43
- return 'react'; // Default to React for Astro
53
+ if (frameworkName === "astro") {
54
+ return "react"; // Default to React for Astro
44
55
  }
45
56
  return null;
46
57
  };
47
58
  // File extensions by framework
48
59
  const getExtensions = (sourceKey, typescript) => {
49
60
  switch (sourceKey) {
50
- case 'vue':
51
- return ['vue'];
52
- case 'svelte':
53
- return ['svelte'];
54
- case 'angular':
55
- return ['component.ts'];
56
- case 'webcomponent':
57
- return ['ts'];
61
+ case "vue":
62
+ return ["vue"];
63
+ case "svelte":
64
+ return ["svelte"];
65
+ case "angular":
66
+ return ["component.ts"];
67
+ case "webcomponent":
68
+ return ["ts"];
58
69
  default: // react
59
- return typescript ? ['tsx', 'jsx'] : ['jsx', 'tsx'];
70
+ return typescript ? ["tsx", "jsx"] : ["jsx", "tsx"];
60
71
  }
61
72
  };
62
73
  // JS Framework config (React, Vue, Svelte, Angular, Web Components)
@@ -67,23 +78,34 @@ function generateConfig(project) {
67
78
  if (sourceKey && !addedSourceKeys.has(sourceKey)) {
68
79
  addedSourceKeys.add(sourceKey);
69
80
  const extensions = getExtensions(sourceKey, framework.typescript);
70
- const jsComponents = project.components.filter(c => c.type === 'jsx' || c.type === 'vue' || c.type === 'svelte' || !c.type);
81
+ const jsComponents = project.components.filter((c) => c.type === "jsx" ||
82
+ c.type === "vue" ||
83
+ c.type === "svelte" ||
84
+ !c.type);
71
85
  let includePatterns;
72
86
  if (jsComponents.length > 0) {
73
- includePatterns = jsComponents.flatMap(c => extensions.map(ext => `${c.path}/**/*.${ext}`));
87
+ includePatterns = jsComponents.flatMap((c) => extensions.map((ext) => `${c.path}/**/*.${ext}`));
74
88
  }
75
89
  else {
76
- includePatterns = extensions.map(ext => `src/**/*.${ext}`);
90
+ // Use default patterns, but expand for monorepo if detected
91
+ const defaultPatterns = extensions.map((ext) => `src/**/*.${ext}`);
92
+ if (monorepoConfig.type) {
93
+ const expanded = expandPatternsForMonorepo(defaultPatterns, monorepoConfig);
94
+ includePatterns = expanded.allPatterns;
95
+ }
96
+ else {
97
+ includePatterns = defaultPatterns;
98
+ }
77
99
  }
78
100
  lines.push(` ${sourceKey}: {`);
79
101
  lines.push(` enabled: true,`);
80
- lines.push(` include: [${includePatterns.map((p) => `'${p}'`).join(', ')}],`);
102
+ lines.push(` include: [${includePatterns.map((p) => `'${p}'`).join(", ")}],`);
81
103
  lines.push(` exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],`);
82
104
  if (project.designSystem) {
83
105
  lines.push(` designSystemPackage: '${project.designSystem.package}',`);
84
106
  }
85
- if (sourceKey === 'webcomponent') {
86
- const wcFramework = framework.name === 'lit' ? 'lit' : 'stencil';
107
+ if (sourceKey === "webcomponent") {
108
+ const wcFramework = framework.name === "lit" ? "lit" : "stencil";
87
109
  lines.push(` framework: '${wcFramework}',`);
88
110
  }
89
111
  lines.push(` },`);
@@ -91,40 +113,97 @@ function generateConfig(project) {
91
113
  }
92
114
  // Server-side / template-based framework config
93
115
  const serverFrameworks = [
94
- 'php', 'laravel', 'symfony',
95
- 'rails',
96
- 'django', 'flask', 'fastapi',
97
- 'express', 'nestjs',
98
- 'spring', 'aspnet',
99
- 'go',
100
- 'hugo', 'jekyll', 'eleventy',
116
+ "php",
117
+ "laravel",
118
+ "symfony",
119
+ "rails",
120
+ "django",
121
+ "flask",
122
+ "fastapi",
123
+ "express",
124
+ "nestjs",
125
+ "spring",
126
+ "aspnet",
127
+ "go",
128
+ "hugo",
129
+ "jekyll",
130
+ "eleventy",
101
131
  ];
102
132
  // Map framework to template type
103
133
  const getTemplateType = (frameworkName, componentType) => {
104
- if (componentType === 'blade')
105
- return 'blade';
106
- if (componentType === 'erb')
107
- return 'erb';
108
- if (componentType === 'twig')
109
- return 'twig';
110
- if (componentType === 'njk')
111
- return 'njk';
134
+ // Return component type directly if it's a known template type
135
+ const knownTypes = [
136
+ // Server-side templates
137
+ "blade", "erb", "twig", "njk", "razor", "hbs", "mustache",
138
+ "ejs", "pug", "liquid", "slim", "haml", "jinja", "django",
139
+ "thymeleaf", "freemarker", "go-template", "edge", "eta", "heex",
140
+ "velocity", "xslt",
141
+ // JS frameworks
142
+ "astro", "solid", "qwik", "marko", "lit", "fast", "angular",
143
+ "stencil", "alpine", "htmx",
144
+ // Static site generators
145
+ "hugo", "jekyll", "eleventy", "shopify",
146
+ // Documentation
147
+ "markdown", "mdx", "asciidoc",
148
+ // Graphics
149
+ "svg",
150
+ // Data templates
151
+ "yaml-template", "json-template"
152
+ ];
153
+ if (componentType && knownTypes.includes(componentType)) {
154
+ return componentType;
155
+ }
112
156
  // Framework-based defaults
113
- if (frameworkName === 'laravel')
114
- return 'blade';
115
- if (frameworkName === 'rails')
116
- return 'erb';
117
- if (frameworkName === 'symfony')
118
- return 'twig';
119
- if (frameworkName === 'eleventy')
120
- return 'njk';
121
- return 'html';
157
+ if (frameworkName === "laravel")
158
+ return "blade";
159
+ if (frameworkName === "rails")
160
+ return "erb";
161
+ if (frameworkName === "symfony")
162
+ return "twig";
163
+ if (frameworkName === "eleventy")
164
+ return "eleventy";
165
+ if (frameworkName === "aspnet")
166
+ return "razor";
167
+ if (frameworkName === "express")
168
+ return "ejs";
169
+ if (frameworkName === "flask")
170
+ return "jinja";
171
+ if (frameworkName === "django")
172
+ return "django";
173
+ if (frameworkName === "spring")
174
+ return "thymeleaf";
175
+ if (frameworkName === "go")
176
+ return "go-template";
177
+ if (frameworkName === "astro")
178
+ return "astro";
179
+ if (frameworkName === "hugo")
180
+ return "hugo";
181
+ if (frameworkName === "jekyll")
182
+ return "jekyll";
183
+ return "html";
122
184
  };
123
185
  // Check if any framework is a server-side framework
124
- const serverFramework = project.frameworks.find(f => serverFrameworks.includes(f.name));
186
+ const serverFramework = project.frameworks.find((f) => serverFrameworks.includes(f.name));
125
187
  if (serverFramework) {
126
- const templateComponents = project.components.filter(c => c.type === 'php' || c.type === 'blade' || c.type === 'erb' ||
127
- c.type === 'twig' || c.type === 'html' || c.type === 'njk');
188
+ const templateTypes = [
189
+ // Server-side templates
190
+ "php", "blade", "erb", "twig", "html", "njk", "razor", "hbs",
191
+ "mustache", "ejs", "pug", "liquid", "slim", "haml", "jinja",
192
+ "django", "thymeleaf", "freemarker", "go-template", "edge",
193
+ "eta", "heex", "velocity", "xslt",
194
+ // JS frameworks
195
+ "astro", "solid", "qwik", "marko", "lit", "fast", "angular",
196
+ "stencil", "alpine", "htmx",
197
+ // Static site generators
198
+ "hugo", "jekyll", "eleventy", "shopify",
199
+ // Documentation
200
+ "markdown", "mdx", "asciidoc",
201
+ // Graphics
202
+ "svg",
203
+ // Data templates
204
+ "yaml-template", "json-template"
205
+ ];
206
+ const templateComponents = project.components.filter((c) => c.type && templateTypes.includes(c.type));
128
207
  if (templateComponents.length > 0) {
129
208
  // Use the first component's type to determine template type
130
209
  const templateType = getTemplateType(serverFramework.name, templateComponents[0]?.type);
@@ -146,8 +225,8 @@ function generateConfig(project) {
146
225
  lines.push(` },`);
147
226
  }
148
227
  // Token files config
149
- const tokenFiles = project.tokens.filter((t) => t.type !== 'tailwind');
150
- const hasTailwind = project.tokens.some((t) => t.type === 'tailwind');
228
+ const tokenFiles = project.tokens.filter((t) => t.type !== "tailwind");
229
+ const hasTailwind = project.tokens.some((t) => t.type === "tailwind");
151
230
  if (tokenFiles.length > 0 || hasTailwind) {
152
231
  lines.push(` tokens: {`);
153
232
  lines.push(` enabled: true,`);
@@ -173,66 +252,84 @@ function generateConfig(project) {
173
252
  lines.push(` },`);
174
253
  lines.push(`};`);
175
254
  lines.push(``);
176
- return lines.join('\n');
255
+ return lines.join("\n");
177
256
  }
178
257
  function printDetectionResults(project) {
179
- console.log('');
180
- console.log(chalk.bold(' Detected:'));
258
+ console.log("");
259
+ console.log(chalk.bold(" Detected:"));
181
260
  const frameworkNames = {
182
261
  // JS frameworks
183
- 'react': 'React',
184
- 'vue': 'Vue',
185
- 'svelte': 'Svelte',
186
- 'angular': 'Angular',
187
- 'solid': 'Solid',
188
- 'preact': 'Preact',
262
+ react: "React",
263
+ vue: "Vue",
264
+ svelte: "Svelte",
265
+ angular: "Angular",
266
+ solid: "Solid",
267
+ preact: "Preact",
189
268
  // Meta-frameworks
190
- 'nextjs': 'Next.js',
191
- 'nuxt': 'Nuxt',
192
- 'astro': 'Astro',
193
- 'remix': 'Remix',
194
- 'sveltekit': 'SvelteKit',
195
- 'gatsby': 'Gatsby',
269
+ nextjs: "Next.js",
270
+ nuxt: "Nuxt",
271
+ astro: "Astro",
272
+ remix: "Remix",
273
+ sveltekit: "SvelteKit",
274
+ gatsby: "Gatsby",
196
275
  // Mobile
197
- 'react-native': 'React Native',
198
- 'flutter': 'Flutter',
199
- 'expo': 'Expo',
276
+ "react-native": "React Native",
277
+ flutter: "Flutter",
278
+ expo: "Expo",
200
279
  // Web Components
201
- 'lit': 'Lit',
202
- 'stencil': 'Stencil',
280
+ lit: "Lit",
281
+ stencil: "Stencil",
203
282
  // Server-side
204
- 'php': 'PHP',
205
- 'laravel': 'Laravel',
206
- 'symfony': 'Symfony',
207
- 'rails': 'Ruby on Rails',
208
- 'django': 'Django',
209
- 'flask': 'Flask',
210
- 'fastapi': 'FastAPI',
211
- 'express': 'Express',
212
- 'nestjs': 'NestJS',
213
- 'spring': 'Spring Boot',
214
- 'aspnet': 'ASP.NET',
215
- 'go': 'Go',
283
+ php: "PHP",
284
+ laravel: "Laravel",
285
+ symfony: "Symfony",
286
+ rails: "Ruby on Rails",
287
+ django: "Django",
288
+ flask: "Flask",
289
+ fastapi: "FastAPI",
290
+ express: "Express",
291
+ nestjs: "NestJS",
292
+ spring: "Spring Boot",
293
+ aspnet: "ASP.NET",
294
+ go: "Go",
216
295
  // Static site generators
217
- 'hugo': 'Hugo',
218
- 'jekyll': 'Jekyll',
219
- 'eleventy': 'Eleventy',
296
+ hugo: "Hugo",
297
+ jekyll: "Jekyll",
298
+ eleventy: "Eleventy",
220
299
  };
221
300
  // Frameworks - show all detected
222
301
  if (project.frameworks.length > 0) {
223
302
  // Show warning if multiple UI frameworks detected (framework sprawl)
224
- const uiFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid', 'preact', 'lit', 'stencil',
225
- 'nextjs', 'nuxt', 'astro', 'remix', 'sveltekit', 'gatsby', 'react-native', 'expo', 'flutter'];
226
- const uiCount = project.frameworks.filter(f => uiFrameworks.includes(f.name)).length;
303
+ const uiFrameworks = [
304
+ "react",
305
+ "vue",
306
+ "svelte",
307
+ "angular",
308
+ "solid",
309
+ "preact",
310
+ "lit",
311
+ "stencil",
312
+ "nextjs",
313
+ "nuxt",
314
+ "astro",
315
+ "remix",
316
+ "sveltekit",
317
+ "gatsby",
318
+ "react-native",
319
+ "expo",
320
+ "flutter",
321
+ ];
322
+ const uiCount = project.frameworks.filter((f) => uiFrameworks.includes(f.name)).length;
227
323
  if (uiCount > 1) {
228
- console.log(chalk.yellow('') + chalk.yellow.bold('Multiple UI frameworks detected (framework sprawl)'));
324
+ console.log(chalk.yellow("") +
325
+ chalk.yellow.bold("Multiple UI frameworks detected (framework sprawl)"));
229
326
  }
230
327
  for (const framework of project.frameworks) {
231
- const ts = framework.typescript ? ' + TypeScript' : '';
328
+ const ts = framework.typescript ? " + TypeScript" : "";
232
329
  const frameworkName = frameworkNames[framework.name] || capitalize(framework.name);
233
- const meta = framework.meta ? chalk.dim(` (${framework.meta})`) : '';
234
- const version = framework.version !== 'unknown' ? ` ${framework.version}` : '';
235
- console.log(chalk.green('') +
330
+ const meta = framework.meta ? chalk.dim(` (${framework.meta})`) : "";
331
+ const version = framework.version !== "unknown" ? ` ${framework.version}` : "";
332
+ console.log(chalk.green("") +
236
333
  chalk.bold(frameworkName) +
237
334
  ts +
238
335
  meta +
@@ -243,18 +340,66 @@ function printDetectionResults(project) {
243
340
  if (project.components.length > 0) {
244
341
  for (const comp of project.components) {
245
342
  const typeLabels = {
246
- 'jsx': 'component files',
247
- 'vue': 'Vue components',
248
- 'svelte': 'Svelte components',
249
- 'php': 'PHP templates',
250
- 'blade': 'Blade templates',
251
- 'erb': 'ERB templates',
252
- 'twig': 'Twig templates',
253
- 'html': 'HTML templates',
254
- 'njk': 'Nunjucks templates',
343
+ // JS frameworks
344
+ jsx: "component files",
345
+ tsx: "TypeScript components",
346
+ vue: "Vue components",
347
+ svelte: "Svelte components",
348
+ astro: "Astro components",
349
+ solid: "Solid components",
350
+ qwik: "Qwik components",
351
+ marko: "Marko components",
352
+ lit: "Lit elements",
353
+ fast: "FAST elements",
354
+ // Server-side templates
355
+ php: "PHP templates",
356
+ blade: "Blade templates",
357
+ erb: "ERB templates",
358
+ twig: "Twig templates",
359
+ html: "HTML templates",
360
+ njk: "Nunjucks templates",
361
+ razor: "Razor views",
362
+ hbs: "Handlebars templates",
363
+ mustache: "Mustache templates",
364
+ ejs: "EJS templates",
365
+ pug: "Pug templates",
366
+ liquid: "Liquid templates",
367
+ slim: "Slim templates",
368
+ haml: "Haml templates",
369
+ jinja: "Jinja templates",
370
+ django: "Django templates",
371
+ thymeleaf: "Thymeleaf templates",
372
+ freemarker: "Freemarker templates",
373
+ velocity: "Velocity templates",
374
+ "go-template": "Go templates",
375
+ edge: "Edge.js templates",
376
+ eta: "Eta templates",
377
+ heex: "HEEx templates",
378
+ xslt: "XSLT stylesheets",
379
+ // Static site generators
380
+ hugo: "Hugo layouts",
381
+ jekyll: "Jekyll layouts",
382
+ eleventy: "Eleventy templates",
383
+ shopify: "Shopify templates",
384
+ // Documentation
385
+ markdown: "Markdown files",
386
+ mdx: "MDX files",
387
+ asciidoc: "AsciiDoc files",
388
+ // Data templates
389
+ "yaml-template": "YAML templates",
390
+ "json-template": "JSON templates",
391
+ // Additional JS frameworks
392
+ angular: "Angular components",
393
+ stencil: "Stencil components",
394
+ alpine: "Alpine.js templates",
395
+ htmx: "HTMX templates",
396
+ // Graphics
397
+ svg: "SVG components",
255
398
  };
256
- const typeLabel = comp.type ? (typeLabels[comp.type] || 'template files') : 'component files';
257
- console.log(chalk.green(' ✓ ') +
399
+ const typeLabel = comp.type
400
+ ? typeLabels[comp.type] || "template files"
401
+ : "component files";
402
+ console.log(chalk.green(" ✓ ") +
258
403
  `${comp.fileCount} ${typeLabel} in ` +
259
404
  chalk.cyan(comp.path));
260
405
  }
@@ -262,36 +407,47 @@ function printDetectionResults(project) {
262
407
  // Tokens
263
408
  if (project.tokens.length > 0) {
264
409
  for (const token of project.tokens) {
265
- const icon = token.type === 'tailwind' ? ' ✓ ' : ' ✓ ';
266
- console.log(chalk.green(icon) + `${token.name}: ` + chalk.cyan(token.path));
410
+ console.log(chalk.green(" ✓ ") + `${token.name}: ` + chalk.cyan(token.path));
267
411
  }
268
412
  }
269
413
  // Storybook
270
414
  if (project.storybook) {
271
- const version = project.storybook.version ? ` (${project.storybook.version})` : '';
272
- console.log(chalk.green(' ✓ ') + `Storybook` + chalk.dim(version));
415
+ const version = project.storybook.version
416
+ ? ` (${project.storybook.version})`
417
+ : "";
418
+ console.log(chalk.green(" ✓ ") + `Storybook` + chalk.dim(version));
273
419
  }
274
420
  // Design system
275
421
  if (project.designSystem) {
276
- console.log(chalk.green('') +
422
+ console.log(chalk.green("") +
277
423
  `Design system: ` +
278
424
  chalk.cyan(project.designSystem.package));
279
425
  }
280
426
  // Monorepo
281
427
  if (project.monorepo) {
282
- console.log(chalk.green(' ✓ ') +
428
+ // Show basic monorepo info
429
+ console.log(chalk.green(" ✓ ") +
283
430
  capitalize(project.monorepo.type) +
284
431
  ` monorepo (${project.monorepo.packages.length} packages)`);
432
+ // Show monorepo component paths if detected
433
+ const monorepoComponents = project.components.filter((c) => c.path.startsWith("packages/") ||
434
+ c.path.startsWith("apps/") ||
435
+ c.path.startsWith("libs/") ||
436
+ c.path.startsWith("modules/"));
437
+ if (monorepoComponents.length > 0) {
438
+ console.log(chalk.dim(" ") +
439
+ chalk.dim(`Scanning: ${monorepoComponents.map((c) => c.path).slice(0, 3).join(", ")}${monorepoComponents.length > 3 ? ` +${monorepoComponents.length - 3} more` : ""}`));
440
+ }
285
441
  }
286
442
  // Nothing found
287
443
  if (project.frameworks.length === 0 &&
288
444
  project.components.length === 0 &&
289
445
  project.tokens.length === 0 &&
290
446
  !project.storybook) {
291
- console.log(chalk.yellow('') + 'No sources auto-detected');
292
- console.log(chalk.dim(' You can manually configure sources in buoy.config.mjs'));
447
+ console.log(chalk.yellow("") + "No sources auto-detected");
448
+ console.log(chalk.dim(" You can manually configure sources in buoy.config.mjs"));
293
449
  }
294
- console.log('');
450
+ console.log("");
295
451
  }
296
452
  function capitalize(s) {
297
453
  return s.charAt(0).toUpperCase() + s.slice(1);
@@ -301,35 +457,36 @@ async function promptConfirm(message, defaultValue = true) {
301
457
  input: process.stdin,
302
458
  output: process.stdout,
303
459
  });
304
- const suffix = defaultValue ? '[Y/n]' : '[y/N]';
460
+ const suffix = defaultValue ? "[Y/n]" : "[y/N]";
305
461
  return new Promise((resolve) => {
306
462
  rl.question(`${message} ${suffix} `, (answer) => {
307
463
  rl.close();
308
464
  const trimmed = answer.trim().toLowerCase();
309
- if (trimmed === '') {
465
+ if (trimmed === "") {
310
466
  resolve(defaultValue);
311
467
  }
312
468
  else {
313
- resolve(trimmed === 'y' || trimmed === 'yes');
469
+ resolve(trimmed === "y" || trimmed === "yes");
314
470
  }
315
471
  });
316
472
  });
317
473
  }
318
474
  export function createInitCommand() {
319
- const cmd = new Command('init')
320
- .description('Initialize Buoy configuration in the current project')
321
- .option('-f, --force', 'Overwrite existing configuration')
322
- .option('-n, --name <name>', 'Project name')
323
- .option('--skip-detect', 'Skip auto-detection and create minimal config')
324
- .option('-y, --yes', 'Auto-install recommended plugins without prompting')
325
- .option('--no-install', 'Skip plugin installation prompts')
475
+ const cmd = new Command("init")
476
+ .description("Initialize Buoy configuration in the current project")
477
+ .option("-f, --force", "Overwrite existing configuration")
478
+ .option("-n, --name <name>", "Project name")
479
+ .option("--skip-detect", "Skip auto-detection and create minimal config")
480
+ .option("-y, --yes", "Auto-install recommended plugins without prompting")
481
+ .option("--no-install", "Skip plugin installation prompts")
482
+ .option("--hooks", "Setup pre-commit hook for drift checking")
326
483
  .action(async (options) => {
327
484
  const cwd = process.cwd();
328
- const configPath = resolve(cwd, 'buoy.config.mjs');
485
+ const configPath = resolve(cwd, "buoy.config.mjs");
329
486
  // Check if config already exists
330
487
  if (existsSync(configPath) && !options.force) {
331
488
  warning(`Configuration already exists at ${configPath}`);
332
- info('Use --force to overwrite');
489
+ info("Use --force to overwrite");
333
490
  return;
334
491
  }
335
492
  let project;
@@ -350,7 +507,7 @@ export function createInitCommand() {
350
507
  }
351
508
  else {
352
509
  // Run auto-detection
353
- const spinner = ora('Scanning project...').start();
510
+ const spinner = ora("Scanning project...").start();
354
511
  try {
355
512
  const detector = new ProjectDetector(cwd);
356
513
  project = await detector.detect();
@@ -361,122 +518,132 @@ export function createInitCommand() {
361
518
  printDetectionResults(project);
362
519
  }
363
520
  catch (err) {
364
- spinner.fail('Detection failed');
521
+ spinner.fail("Detection failed");
365
522
  const message = err instanceof Error ? err.message : String(err);
366
523
  error(message);
367
524
  process.exit(1);
368
525
  }
369
526
  }
370
- // Suggest plugins based on lightweight framework detection
527
+ // Show detected frameworks and scanners
371
528
  const detectedFrameworks = await detectFrameworks(cwd);
372
- const installedPlugins = await discoverPlugins();
373
529
  if (detectedFrameworks.length > 0) {
374
- console.log(chalk.bold(' Suggested Plugins'));
375
- console.log('');
376
- for (const fw of detectedFrameworks) {
377
- const installed = installedPlugins.some((p) => p.includes(fw.plugin));
378
- const pluginInfo = PLUGIN_INFO[fw.plugin];
379
- const pluginName = pluginInfo?.name || `@buoy-design/plugin-${fw.plugin}`;
380
- // Plugin header with status
381
- const statusText = installed
382
- ? chalk.green('installed ✓')
383
- : chalk.yellow('not installed');
384
- console.log(` ${chalk.dim('┌')} ${chalk.cyan.bold(pluginName)}${' '.repeat(Math.max(1, 50 - pluginName.length))}${statusText}`);
385
- console.log(` ${chalk.dim('│')}`);
386
- // What was detected
387
- const detectsLabel = pluginInfo?.detects || capitalize(fw.name);
388
- if (fw.matchedFiles && fw.matchedFiles.length > 0) {
389
- console.log(` ${chalk.dim('│')} ${chalk.white('Detected:')} ${detectsLabel}`);
390
- const filesToShow = fw.matchedFiles.slice(0, 3);
391
- for (const file of filesToShow) {
392
- console.log(` ${chalk.dim('│')} ${chalk.dim('•')} ${chalk.cyan(file)}`);
393
- }
394
- if (fw.matchedFiles.length > 3) {
395
- console.log(` ${chalk.dim('│')} ${chalk.dim(` ...and ${fw.matchedFiles.length - 3} more`)}`);
396
- }
397
- }
398
- else {
399
- // Package-based detection
400
- console.log(` ${chalk.dim('│')} ${chalk.white('Detected:')} ${detectsLabel} ${chalk.dim(`(${fw.evidence.toLowerCase()})`)}`);
401
- }
402
- console.log(` ${chalk.dim('│')}`);
403
- // What the plugin does
404
- if (pluginInfo?.description) {
405
- console.log(` ${chalk.dim('│')} ${chalk.white('What it does:')}`);
406
- // Word wrap description at ~60 chars
407
- const words = pluginInfo.description.split(' ');
408
- let line = '';
409
- for (const word of words) {
410
- if (line.length + word.length > 55) {
411
- console.log(` ${chalk.dim('│')} ${chalk.dim(line.trim())}`);
412
- line = word + ' ';
413
- }
414
- else {
415
- line += word + ' ';
416
- }
417
- }
418
- if (line.trim()) {
419
- console.log(` ${chalk.dim('│')} ${chalk.dim(line.trim())}`);
420
- }
530
+ // Separate built-in scanners from optional plugins
531
+ const builtIn = detectedFrameworks.filter((fw) => fw.scanner);
532
+ const optionalPlugins = detectedFrameworks.filter((fw) => fw.plugin && !fw.scanner);
533
+ // Show built-in scanners (no install needed)
534
+ if (builtIn.length > 0) {
535
+ console.log(chalk.bold(" Built-in Scanners") +
536
+ chalk.dim(" (no install needed)"));
537
+ console.log("");
538
+ for (const fw of builtIn) {
539
+ const scannerInfo = BUILTIN_SCANNERS[fw.scanner];
540
+ const scannerLabel = scannerInfo?.description || capitalize(fw.name);
541
+ console.log(` ${chalk.green("✓")} ${chalk.cyan.bold(scannerLabel)}`);
542
+ console.log(` ${chalk.dim(fw.evidence)}`);
543
+ console.log("");
421
544
  }
422
- // Footer with install command or ready status
423
- console.log(` ${chalk.dim('│')}`);
424
- if (installed) {
425
- console.log(` ${chalk.dim('└─')} ${chalk.green('Ready to use')}`);
426
- }
427
- else {
428
- console.log(` ${chalk.dim('└─')} ${chalk.dim(getPluginInstallCommand([fw.plugin]))}`);
429
- }
430
- console.log('');
431
545
  }
432
- const missingPlugins = detectedFrameworks
433
- .map((fw) => fw.plugin)
434
- .filter((plugin, index, self) => self.indexOf(plugin) === index) // unique
435
- .filter((plugin) => !installedPlugins.some((p) => p.includes(plugin)));
436
- if (missingPlugins.length > 0) {
437
- console.log(chalk.dim(' ' + '─'.repeat(65)));
438
- console.log('');
439
- console.log(chalk.bold(' Install all missing plugins:'));
440
- console.log(` ${chalk.cyan(getPluginInstallCommand(missingPlugins))}`);
441
- console.log('');
442
- // Determine if we should install plugins
443
- let shouldInstall = false;
444
- if (options.yes) {
445
- shouldInstall = true;
446
- }
447
- else if (options.install !== false) {
448
- // Only prompt if --no-install was not passed and stdin is a TTY
449
- if (process.stdin.isTTY) {
450
- shouldInstall = await promptConfirm('Install recommended plugins now?', true);
546
+ // Show optional plugins (need install)
547
+ if (optionalPlugins.length > 0) {
548
+ console.log(chalk.bold(" Optional Plugins"));
549
+ console.log("");
550
+ for (const fw of optionalPlugins) {
551
+ const pluginInfo = PLUGIN_INFO[fw.plugin];
552
+ const pluginName = pluginInfo?.name || `@buoy-design/plugin-${fw.plugin}`;
553
+ console.log(` ${chalk.dim("┌")} ${chalk.cyan.bold(pluginName)}`);
554
+ console.log(` ${chalk.dim("│")}`);
555
+ // What was detected
556
+ const detectsLabel = pluginInfo?.detects || capitalize(fw.name);
557
+ console.log(` ${chalk.dim("│")} ${chalk.white("Detected:")} ${detectsLabel} ${chalk.dim(`(${fw.evidence.toLowerCase()})`)}`);
558
+ console.log(` ${chalk.dim("│")}`);
559
+ // What the plugin does
560
+ if (pluginInfo?.description) {
561
+ console.log(` ${chalk.dim("│")} ${chalk.dim(pluginInfo.description)}`);
451
562
  }
563
+ console.log(` ${chalk.dim("└─")} ${chalk.dim(getPluginInstallCommand([fw.plugin]))}`);
564
+ console.log("");
452
565
  }
453
- if (shouldInstall) {
454
- const { execSync } = await import('node:child_process');
455
- console.log('');
456
- console.log('Installing plugins...');
457
- try {
458
- execSync(getPluginInstallCommand(missingPlugins), { stdio: 'inherit' });
459
- success('Plugins installed successfully');
566
+ const missingPlugins = optionalPlugins
567
+ .map((fw) => fw.plugin)
568
+ .filter((plugin, index, self) => self.indexOf(plugin) === index);
569
+ if (missingPlugins.length > 0) {
570
+ console.log(chalk.dim(" " + "─".repeat(65)));
571
+ console.log("");
572
+ console.log(chalk.bold(" Install all optional plugins:"));
573
+ console.log(` ${chalk.cyan(getPluginInstallCommand(missingPlugins))}`);
574
+ console.log("");
575
+ // Determine if we should install plugins
576
+ let shouldInstall = false;
577
+ if (options.yes) {
578
+ shouldInstall = true;
579
+ }
580
+ else if (options.install !== false) {
581
+ // Only prompt if --no-install was not passed and stdin is a TTY
582
+ if (process.stdin.isTTY) {
583
+ shouldInstall = await promptConfirm("Install optional plugins now?", true);
584
+ }
460
585
  }
461
- catch {
462
- warning('Plugin installation failed. You can install manually with the command above.');
586
+ if (shouldInstall) {
587
+ const { execSync } = await import("node:child_process");
588
+ console.log("");
589
+ console.log("Installing plugins...");
590
+ try {
591
+ execSync(getPluginInstallCommand(missingPlugins), {
592
+ stdio: "inherit",
593
+ });
594
+ success("Plugins installed successfully");
595
+ }
596
+ catch {
597
+ warning("Plugin installation failed. You can install manually with the command above.");
598
+ }
463
599
  }
464
600
  }
465
601
  }
466
- console.log('');
602
+ console.log("");
467
603
  }
468
604
  // Generate and write config
469
605
  const content = generateConfig(project);
470
606
  try {
471
- writeFileSync(configPath, content, 'utf-8');
607
+ writeFileSync(configPath, content, "utf-8");
472
608
  success(`Created buoy.config.mjs`);
473
- console.log('');
474
- info('Next steps:');
475
- info(' 1. Run ' + chalk.cyan('buoy scan') + ' to scan your codebase');
476
- info(' 2. Run ' + chalk.cyan('buoy drift check') + ' to detect drift');
609
+ // Setup hooks if --hooks flag is provided
610
+ if (options.hooks) {
611
+ console.log("");
612
+ const hookSystem = detectHookSystem(cwd);
613
+ if (hookSystem) {
614
+ info(`Detected hook system: ${hookSystem}`);
615
+ const hookResult = setupHooks(cwd);
616
+ if (hookResult.success) {
617
+ success(hookResult.message);
618
+ }
619
+ else {
620
+ warning(hookResult.message);
621
+ }
622
+ }
623
+ else {
624
+ // No hook system detected, create standalone hook
625
+ const standaloneResult = generateStandaloneHook(cwd);
626
+ if (standaloneResult.success) {
627
+ success(standaloneResult.message);
628
+ info("To use this hook, copy it to .git/hooks/pre-commit or configure your hook system");
629
+ }
630
+ else {
631
+ warning(standaloneResult.message);
632
+ }
633
+ }
634
+ }
635
+ console.log("");
636
+ info("Next steps:");
637
+ info(" 1. Run " + chalk.cyan("buoy scan") + " to scan your codebase");
638
+ info(" 2. Run " + chalk.cyan("buoy drift check") + " to detect drift");
639
+ if (!options.hooks) {
640
+ info(" 3. Run " +
641
+ chalk.cyan("buoy init --hooks") +
642
+ " to setup pre-commit hooks");
643
+ }
477
644
  if (!project.storybook) {
478
- console.log('');
479
- info(chalk.dim('Optional: Connect Figma by adding your API key to the config'));
645
+ console.log("");
646
+ info(chalk.dim("Optional: Connect Figma by adding your API key to the config"));
480
647
  }
481
648
  }
482
649
  catch (err) {