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