@buoy-design/cli 0.1.30 → 0.2.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.
- package/assets/commands/contribution-loop.md +195 -0
- package/dist/bin.js +0 -0
- package/dist/cloud/config.d.ts +8 -0
- package/dist/cloud/config.d.ts.map +1 -1
- package/dist/cloud/config.js +10 -0
- package/dist/cloud/config.js.map +1 -1
- package/dist/cloud/feature-gate.d.ts +30 -0
- package/dist/cloud/feature-gate.d.ts.map +1 -0
- package/dist/cloud/feature-gate.js +138 -0
- package/dist/cloud/feature-gate.js.map +1 -0
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.d.ts.map +1 -1
- package/dist/cloud/index.js +1 -0
- package/dist/cloud/index.js.map +1 -1
- package/dist/commands/anchor.js +1 -1
- package/dist/commands/anchor.js.map +1 -1
- package/dist/commands/begin.d.ts.map +1 -1
- package/dist/commands/begin.js +118 -5
- package/dist/commands/begin.js.map +1 -1
- package/dist/commands/billing.d.ts +1 -1
- package/dist/commands/billing.js +22 -18
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/bot.d.ts +9 -0
- package/dist/commands/bot.d.ts.map +1 -0
- package/dist/commands/bot.js +258 -0
- package/dist/commands/bot.js.map +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +16 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/cloud.d.ts +15 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +36 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/commands.d.ts +8 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +148 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/commands/components-query.d.ts +13 -0
- package/dist/commands/components-query.d.ts.map +1 -0
- package/dist/commands/components-query.js +263 -0
- package/dist/commands/components-query.js.map +1 -0
- package/dist/commands/components.d.ts +8 -0
- package/dist/commands/components.d.ts.map +1 -0
- package/dist/commands/components.js +14 -0
- package/dist/commands/components.js.map +1 -0
- package/dist/commands/dock.d.ts +13 -0
- package/dist/commands/dock.d.ts.map +1 -1
- package/dist/commands/dock.js +594 -832
- package/dist/commands/dock.js.map +1 -1
- package/dist/commands/drift.d.ts.map +1 -1
- package/dist/commands/drift.js +4 -13
- package/dist/commands/drift.js.map +1 -1
- package/dist/commands/fix.js +1 -1
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/github.d.ts.map +1 -1
- package/dist/commands/github.js +8 -6
- package/dist/commands/github.js.map +1 -1
- package/dist/commands/history.js +2 -2
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/index.d.ts +2 -22
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -23
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/learn.d.ts +9 -0
- package/dist/commands/learn.d.ts.map +1 -0
- package/dist/commands/learn.js +244 -0
- package/dist/commands/learn.js.map +1 -0
- package/dist/commands/link.js +2 -2
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +12 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +64 -1
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/plans.d.ts +9 -0
- package/dist/commands/plans.d.ts.map +1 -0
- package/dist/commands/plans.js +140 -0
- package/dist/commands/plans.js.map +1 -0
- package/dist/commands/ship.d.ts +14 -0
- package/dist/commands/ship.d.ts.map +1 -0
- package/dist/commands/ship.js +342 -0
- package/dist/commands/ship.js.map +1 -0
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +340 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +13 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tokens-lookup.d.ts +14 -0
- package/dist/commands/tokens-lookup.d.ts.map +1 -0
- package/dist/commands/tokens-lookup.js +300 -0
- package/dist/commands/tokens-lookup.js.map +1 -0
- package/dist/commands/tokens.d.ts.map +1 -1
- package/dist/commands/tokens.js +6 -1
- package/dist/commands/tokens.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +60 -3
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config/schema.d.ts +132 -132
- package/dist/fix/applier.js +1 -1
- package/dist/fix/applier.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +31 -3
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -43
- package/dist/index.js.map +1 -1
- package/dist/insights/project-insights.d.ts.map +1 -1
- package/dist/insights/project-insights.js +0 -8
- package/dist/insights/project-insights.js.map +1 -1
- package/dist/output/fix-formatters.d.ts.map +1 -1
- package/dist/output/fix-formatters.js +10 -2
- package/dist/output/fix-formatters.js.map +1 -1
- package/dist/services/context-generator.js +2 -2
- package/dist/services/context-generator.js.map +1 -1
- package/dist/services/skill-export.d.ts.map +1 -1
- package/dist/services/skill-export.js +39 -4
- package/dist/services/skill-export.js.map +1 -1
- package/dist/wizard/ai-guardrails-generator.js +1 -1
- package/package.json +16 -15
- package/LICENSE +0 -21
- package/dist/commands/explain.d.ts +0 -3
- package/dist/commands/explain.js +0 -224
- package/dist/commands/explain.js.map +0 -1
- package/dist/explain/agents.d.ts +0 -31
- package/dist/explain/agents.d.ts.map +0 -1
- package/dist/explain/agents.js +0 -507
- package/dist/explain/agents.js.map +0 -1
package/dist/commands/dock.js
CHANGED
|
@@ -1,885 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buoy dock - Dock Buoy into your project
|
|
3
|
+
*
|
|
4
|
+
* Smart walkthrough that sets up config, agents, and hooks.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* buoy dock # Smart walkthrough: config → agents → hooks
|
|
8
|
+
* buoy dock config # Just create buoy.config.mjs
|
|
9
|
+
* buoy dock agents # Set up AI agents (skills + context)
|
|
10
|
+
* buoy dock skills # Just create skill files
|
|
11
|
+
* buoy dock context # Just generate CLAUDE.md section
|
|
12
|
+
* buoy dock hooks # Just set up git hooks
|
|
13
|
+
*/
|
|
1
14
|
import { Command } from "commander";
|
|
2
|
-
import { writeFileSync, existsSync, readFileSync } from "fs";
|
|
3
|
-
import { resolve,
|
|
15
|
+
import { writeFileSync, existsSync, readFileSync, mkdirSync, appendFileSync, copyFileSync, readdirSync } from "fs";
|
|
16
|
+
import { resolve, dirname, join } from "path";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
4
19
|
import chalk from "chalk";
|
|
5
|
-
import ora from "ora";
|
|
6
20
|
import { createInterface } from "readline";
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
21
|
+
import ora from "ora";
|
|
22
|
+
import { success, error, info, warning, setJsonMode, spinner } from "../output/reporters.js";
|
|
23
|
+
import { loadConfig, getConfigPath } from "../config/loader.js";
|
|
24
|
+
import { buildAutoConfig } from "../config/auto-detect.js";
|
|
25
|
+
import { ScanOrchestrator } from "../scan/orchestrator.js";
|
|
26
|
+
import { SkillExportService } from "../services/skill-export.js";
|
|
27
|
+
import { generateContext } from "../services/context-generator.js";
|
|
28
|
+
import { setupHooks, generateStandaloneHook, detectHookSystem, setupClaudeHooks } from "../hooks/index.js";
|
|
29
|
+
import { ProjectDetector, detectMonorepoConfig, expandPatternsForMonorepo } from "../detect/index.js";
|
|
30
|
+
import { detectFrameworks, getPluginInstallCommand } from "../detect/frameworks.js";
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
const __dirname = dirname(__filename);
|
|
33
|
+
export function createDockCommand() {
|
|
34
|
+
const cmd = new Command("dock")
|
|
35
|
+
.description("Dock Buoy into your project")
|
|
36
|
+
.option("-y, --yes", "Auto-accept all defaults")
|
|
37
|
+
.option("--json", "Output results as JSON")
|
|
38
|
+
.action(async (options) => {
|
|
39
|
+
await runSmartDock(options);
|
|
40
|
+
});
|
|
41
|
+
// dock config - Just create config
|
|
42
|
+
cmd
|
|
43
|
+
.command("config")
|
|
44
|
+
.description("Create buoy.config.mjs")
|
|
45
|
+
.option("-f, --force", "Overwrite existing configuration")
|
|
46
|
+
.option("-y, --yes", "Auto-install recommended plugins without prompting")
|
|
47
|
+
.option("--skip-detect", "Skip auto-detection and create minimal config")
|
|
48
|
+
.action(async (options) => {
|
|
49
|
+
await runConfigDock(options);
|
|
50
|
+
});
|
|
51
|
+
// dock agents - Full AI agent onboarding
|
|
52
|
+
cmd
|
|
53
|
+
.command("agents")
|
|
54
|
+
.description("Set up AI agents with your design system")
|
|
55
|
+
.option("--dry-run", "Show what would be created without writing files")
|
|
56
|
+
.option("--json", "Output result as JSON")
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
await runAgentsDock(options);
|
|
59
|
+
});
|
|
60
|
+
// dock skills - Create skill files
|
|
61
|
+
cmd
|
|
62
|
+
.command("skills")
|
|
63
|
+
.description("Create skill files for AI agents")
|
|
64
|
+
.option("-o, --output <path>", "Output directory", ".claude/skills/design-system")
|
|
65
|
+
.option("--global", "Export to global skills directory (~/.claude/skills/)")
|
|
66
|
+
.option("--dry-run", "Show what would be created without writing files")
|
|
67
|
+
.option("--json", "Output result as JSON")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
await runSkillsDock(options);
|
|
70
|
+
});
|
|
71
|
+
// dock context - Generate CLAUDE.md section
|
|
72
|
+
cmd
|
|
73
|
+
.command("context")
|
|
74
|
+
.description("Generate design system context for CLAUDE.md")
|
|
75
|
+
.option("-o, --output <path>", "Output file path (default: CLAUDE.md)")
|
|
76
|
+
.option("--stdout", "Output to stdout instead of file")
|
|
77
|
+
.option("-d, --detail <level>", "Detail level: minimal, standard, comprehensive", "standard")
|
|
78
|
+
.option("--json", "Output as JSON with stats")
|
|
79
|
+
.action(async (options) => {
|
|
80
|
+
await runContextDock(options);
|
|
81
|
+
});
|
|
82
|
+
// dock hooks - Set up git hooks
|
|
83
|
+
cmd
|
|
84
|
+
.command("hooks")
|
|
85
|
+
.description("Set up git hooks for drift checking")
|
|
86
|
+
.option("--claude", "Also set up Claude Code hooks")
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
await runHooksDock(options);
|
|
89
|
+
});
|
|
90
|
+
return cmd;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Smart dock walkthrough - goes through config → agents → hooks
|
|
94
|
+
*/
|
|
95
|
+
async function runSmartDock(options) {
|
|
96
|
+
const cwd = process.cwd();
|
|
97
|
+
if (options.json) {
|
|
98
|
+
setJsonMode(true);
|
|
99
|
+
}
|
|
100
|
+
console.log("");
|
|
101
|
+
console.log(chalk.cyan.bold(" Docking Buoy..."));
|
|
102
|
+
console.log(chalk.dim(" ─".repeat(25)));
|
|
103
|
+
console.log("");
|
|
104
|
+
const results = {
|
|
105
|
+
configCreated: false,
|
|
106
|
+
agentsSetup: false,
|
|
107
|
+
hooksSetup: false,
|
|
58
108
|
};
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return ["ts"];
|
|
70
|
-
default: // react
|
|
71
|
-
return typescript ? ["tsx", "jsx"] : ["jsx", "tsx"];
|
|
109
|
+
// Step 1: Check/create config
|
|
110
|
+
const configPath = getConfigPath();
|
|
111
|
+
if (!configPath) {
|
|
112
|
+
console.log(chalk.yellow(" ⚠ No buoy.config.mjs found"));
|
|
113
|
+
console.log("");
|
|
114
|
+
const shouldCreateConfig = options.yes || await promptConfirm(" Create config now?", true);
|
|
115
|
+
if (shouldCreateConfig) {
|
|
116
|
+
console.log("");
|
|
117
|
+
await runConfigDock({ yes: options.yes });
|
|
118
|
+
results.configCreated = true;
|
|
72
119
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const sourceKey = getSourceKey(framework.name);
|
|
79
|
-
if (sourceKey && !addedSourceKeys.has(sourceKey)) {
|
|
80
|
-
addedSourceKeys.add(sourceKey);
|
|
81
|
-
const extensions = getExtensions(sourceKey, framework.typescript);
|
|
82
|
-
const jsComponents = project.components.filter((c) => c.type === "jsx" ||
|
|
83
|
-
c.type === "vue" ||
|
|
84
|
-
c.type === "svelte" ||
|
|
85
|
-
!c.type);
|
|
86
|
-
let includePatterns;
|
|
87
|
-
if (jsComponents.length > 0) {
|
|
88
|
-
includePatterns = jsComponents.flatMap((c) => extensions.map((ext) => `${c.path}/**/*.${ext}`));
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Use default patterns, but expand for monorepo if detected
|
|
92
|
-
const defaultPatterns = extensions.map((ext) => `src/**/*.${ext}`);
|
|
93
|
-
if (monorepoConfig.type) {
|
|
94
|
-
const expanded = expandPatternsForMonorepo(defaultPatterns, monorepoConfig);
|
|
95
|
-
includePatterns = expanded.allPatterns;
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
includePatterns = defaultPatterns;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
lines.push(` ${sourceKey}: {`);
|
|
102
|
-
lines.push(` enabled: true,`);
|
|
103
|
-
lines.push(` include: [${includePatterns.map((p) => `'${p}'`).join(", ")}],`);
|
|
104
|
-
lines.push(` exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],`);
|
|
105
|
-
if (project.designSystem) {
|
|
106
|
-
lines.push(` designSystemPackage: '${project.designSystem.package}',`);
|
|
107
|
-
}
|
|
108
|
-
if (sourceKey === "webcomponent") {
|
|
109
|
-
const wcFramework = framework.name === "lit" ? "lit" : "stencil";
|
|
110
|
-
lines.push(` framework: '${wcFramework}',`);
|
|
111
|
-
}
|
|
112
|
-
lines.push(` },`);
|
|
120
|
+
else {
|
|
121
|
+
console.log("");
|
|
122
|
+
info("Run `buoy dock config` later to create config.");
|
|
123
|
+
console.log("");
|
|
124
|
+
return;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Server-side templates
|
|
138
|
-
"blade", "erb", "twig", "njk", "razor", "hbs", "mustache",
|
|
139
|
-
"ejs", "pug", "liquid", "slim", "haml", "jinja", "django",
|
|
140
|
-
"thymeleaf", "freemarker", "go-template", "edge", "eta", "heex",
|
|
141
|
-
"velocity", "xslt",
|
|
142
|
-
// JS frameworks
|
|
143
|
-
"astro", "solid", "qwik", "marko", "lit", "fast", "angular",
|
|
144
|
-
"stencil", "alpine", "htmx",
|
|
145
|
-
// Static site generators
|
|
146
|
-
"hugo", "jekyll", "eleventy", "shopify",
|
|
147
|
-
// Documentation
|
|
148
|
-
"markdown", "mdx", "asciidoc",
|
|
149
|
-
// Graphics
|
|
150
|
-
"svg",
|
|
151
|
-
// Data templates
|
|
152
|
-
"yaml-template", "json-template"
|
|
153
|
-
];
|
|
154
|
-
if (componentType && knownTypes.includes(componentType)) {
|
|
155
|
-
return componentType;
|
|
156
|
-
}
|
|
157
|
-
// Framework-based defaults
|
|
158
|
-
if (frameworkName === "laravel")
|
|
159
|
-
return "blade";
|
|
160
|
-
if (frameworkName === "rails")
|
|
161
|
-
return "erb";
|
|
162
|
-
if (frameworkName === "symfony")
|
|
163
|
-
return "twig";
|
|
164
|
-
if (frameworkName === "eleventy")
|
|
165
|
-
return "eleventy";
|
|
166
|
-
if (frameworkName === "aspnet")
|
|
167
|
-
return "razor";
|
|
168
|
-
if (frameworkName === "express")
|
|
169
|
-
return "ejs";
|
|
170
|
-
if (frameworkName === "flask")
|
|
171
|
-
return "jinja";
|
|
172
|
-
if (frameworkName === "django")
|
|
173
|
-
return "django";
|
|
174
|
-
if (frameworkName === "spring")
|
|
175
|
-
return "thymeleaf";
|
|
176
|
-
if (frameworkName === "go")
|
|
177
|
-
return "go-template";
|
|
178
|
-
if (frameworkName === "astro")
|
|
179
|
-
return "astro";
|
|
180
|
-
if (frameworkName === "hugo")
|
|
181
|
-
return "hugo";
|
|
182
|
-
if (frameworkName === "jekyll")
|
|
183
|
-
return "jekyll";
|
|
184
|
-
return "html";
|
|
185
|
-
};
|
|
186
|
-
// Check if any framework is a server-side framework
|
|
187
|
-
const serverFramework = project.frameworks.find((f) => serverFrameworks.includes(f.name));
|
|
188
|
-
if (serverFramework) {
|
|
189
|
-
const templateTypes = [
|
|
190
|
-
// Server-side templates
|
|
191
|
-
"php", "blade", "erb", "twig", "html", "njk", "razor", "hbs",
|
|
192
|
-
"mustache", "ejs", "pug", "liquid", "slim", "haml", "jinja",
|
|
193
|
-
"django", "thymeleaf", "freemarker", "go-template", "edge",
|
|
194
|
-
"eta", "heex", "velocity", "xslt",
|
|
195
|
-
// JS frameworks
|
|
196
|
-
"astro", "solid", "qwik", "marko", "lit", "fast", "angular",
|
|
197
|
-
"stencil", "alpine", "htmx",
|
|
198
|
-
// Static site generators
|
|
199
|
-
"hugo", "jekyll", "eleventy", "shopify",
|
|
200
|
-
// Documentation
|
|
201
|
-
"markdown", "mdx", "asciidoc",
|
|
202
|
-
// Graphics
|
|
203
|
-
"svg",
|
|
204
|
-
// Data templates
|
|
205
|
-
"yaml-template", "json-template"
|
|
206
|
-
];
|
|
207
|
-
const templateComponents = project.components.filter((c) => c.type && templateTypes.includes(c.type));
|
|
208
|
-
if (templateComponents.length > 0) {
|
|
209
|
-
// Use the first component's type to determine template type
|
|
210
|
-
const templateType = getTemplateType(serverFramework.name, templateComponents[0]?.type);
|
|
211
|
-
lines.push(` templates: {`);
|
|
212
|
-
lines.push(` enabled: true,`);
|
|
213
|
-
lines.push(` type: '${templateType}',`);
|
|
214
|
-
lines.push(` include: [`);
|
|
215
|
-
for (const comp of templateComponents) {
|
|
216
|
-
lines.push(` '${comp.pattern}',`);
|
|
217
|
-
}
|
|
218
|
-
lines.push(` ],`);
|
|
219
|
-
lines.push(` },`);
|
|
127
|
+
else {
|
|
128
|
+
results.configCreated = true;
|
|
129
|
+
console.log(` ${chalk.green("✓")} Config exists`);
|
|
130
|
+
}
|
|
131
|
+
// Step 2: Check/setup AI agents
|
|
132
|
+
const skillsDir = resolve(cwd, ".claude/skills/design-system");
|
|
133
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
134
|
+
const hasSkills = existsSync(skillsDir);
|
|
135
|
+
const hasClaudeMd = existsSync(claudeMdPath) && readFileSync(claudeMdPath, "utf-8").includes("Design System");
|
|
136
|
+
if (!hasSkills || !hasClaudeMd) {
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log(chalk.yellow(" ⚠ AI agents not configured"));
|
|
139
|
+
if (!hasSkills)
|
|
140
|
+
console.log(chalk.dim(" • Missing skill files"));
|
|
141
|
+
if (!hasClaudeMd)
|
|
142
|
+
console.log(chalk.dim(" • Missing CLAUDE.md section"));
|
|
143
|
+
console.log("");
|
|
144
|
+
const shouldSetupAgents = options.yes || await promptConfirm(" Set up AI agents?", true);
|
|
145
|
+
if (shouldSetupAgents) {
|
|
146
|
+
console.log("");
|
|
147
|
+
await runAgentsDock({ json: options.json });
|
|
148
|
+
results.agentsSetup = true;
|
|
220
149
|
}
|
|
221
150
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
lines.push(` ],`);
|
|
151
|
+
else {
|
|
152
|
+
console.log(` ${chalk.green("✓")} AI agents configured`);
|
|
153
|
+
results.agentsSetup = true;
|
|
154
|
+
}
|
|
155
|
+
// Step 3: Check/setup hooks
|
|
156
|
+
const hasHooks = existsSync(join(cwd, ".husky/pre-commit")) ||
|
|
157
|
+
existsSync(join(cwd, "lefthook.yml")) ||
|
|
158
|
+
existsSync(join(cwd, ".git/hooks/pre-commit"));
|
|
159
|
+
if (!hasHooks) {
|
|
160
|
+
console.log("");
|
|
161
|
+
console.log(chalk.yellow(" ⚠ No pre-commit hooks"));
|
|
162
|
+
console.log("");
|
|
163
|
+
const shouldSetupHooks = options.yes || await promptConfirm(" Set up hooks?", true);
|
|
164
|
+
if (shouldSetupHooks) {
|
|
165
|
+
await runHooksDock({});
|
|
166
|
+
results.hooksSetup = true;
|
|
240
167
|
}
|
|
241
|
-
lines.push(` },`);
|
|
242
168
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
lines.push(` // accessToken: process.env.FIGMA_ACCESS_TOKEN,`);
|
|
247
|
-
lines.push(` // fileKeys: [],`);
|
|
248
|
-
lines.push(` },`);
|
|
249
|
-
lines.push(` },`);
|
|
250
|
-
lines.push(` output: {`);
|
|
251
|
-
lines.push(` format: 'table',`);
|
|
252
|
-
lines.push(` colors: true,`);
|
|
253
|
-
lines.push(` },`);
|
|
254
|
-
lines.push(`};`);
|
|
255
|
-
lines.push(``);
|
|
256
|
-
return lines.join("\n");
|
|
257
|
-
}
|
|
258
|
-
function printDetectionResults(project) {
|
|
259
|
-
console.log("");
|
|
260
|
-
console.log(chalk.bold(" Detected:"));
|
|
261
|
-
const frameworkNames = {
|
|
262
|
-
// JS frameworks
|
|
263
|
-
react: "React",
|
|
264
|
-
vue: "Vue",
|
|
265
|
-
svelte: "Svelte",
|
|
266
|
-
angular: "Angular",
|
|
267
|
-
solid: "Solid",
|
|
268
|
-
preact: "Preact",
|
|
269
|
-
// Meta-frameworks
|
|
270
|
-
nextjs: "Next.js",
|
|
271
|
-
nuxt: "Nuxt",
|
|
272
|
-
astro: "Astro",
|
|
273
|
-
remix: "Remix",
|
|
274
|
-
sveltekit: "SvelteKit",
|
|
275
|
-
gatsby: "Gatsby",
|
|
276
|
-
// Mobile
|
|
277
|
-
"react-native": "React Native",
|
|
278
|
-
flutter: "Flutter",
|
|
279
|
-
expo: "Expo",
|
|
280
|
-
// Web Components
|
|
281
|
-
lit: "Lit",
|
|
282
|
-
stencil: "Stencil",
|
|
283
|
-
// Server-side
|
|
284
|
-
php: "PHP",
|
|
285
|
-
laravel: "Laravel",
|
|
286
|
-
symfony: "Symfony",
|
|
287
|
-
rails: "Ruby on Rails",
|
|
288
|
-
django: "Django",
|
|
289
|
-
flask: "Flask",
|
|
290
|
-
fastapi: "FastAPI",
|
|
291
|
-
express: "Express",
|
|
292
|
-
nestjs: "NestJS",
|
|
293
|
-
spring: "Spring Boot",
|
|
294
|
-
aspnet: "ASP.NET",
|
|
295
|
-
go: "Go",
|
|
296
|
-
// Static site generators
|
|
297
|
-
hugo: "Hugo",
|
|
298
|
-
jekyll: "Jekyll",
|
|
299
|
-
eleventy: "Eleventy",
|
|
300
|
-
};
|
|
301
|
-
// Frameworks - show all detected
|
|
302
|
-
if (project.frameworks.length > 0) {
|
|
303
|
-
// Show warning if multiple UI frameworks detected (framework sprawl)
|
|
304
|
-
const uiFrameworks = [
|
|
305
|
-
"react",
|
|
306
|
-
"vue",
|
|
307
|
-
"svelte",
|
|
308
|
-
"angular",
|
|
309
|
-
"solid",
|
|
310
|
-
"preact",
|
|
311
|
-
"lit",
|
|
312
|
-
"stencil",
|
|
313
|
-
"nextjs",
|
|
314
|
-
"nuxt",
|
|
315
|
-
"astro",
|
|
316
|
-
"remix",
|
|
317
|
-
"sveltekit",
|
|
318
|
-
"gatsby",
|
|
319
|
-
"react-native",
|
|
320
|
-
"expo",
|
|
321
|
-
"flutter",
|
|
322
|
-
];
|
|
323
|
-
const uiCount = project.frameworks.filter((f) => uiFrameworks.includes(f.name)).length;
|
|
324
|
-
if (uiCount > 1) {
|
|
325
|
-
console.log(chalk.yellow(" ⚠ ") +
|
|
326
|
-
chalk.yellow.bold("Multiple UI frameworks detected (framework sprawl)"));
|
|
327
|
-
}
|
|
328
|
-
for (const framework of project.frameworks) {
|
|
329
|
-
const ts = framework.typescript ? " + TypeScript" : "";
|
|
330
|
-
const frameworkName = frameworkNames[framework.name] || capitalize(framework.name);
|
|
331
|
-
const meta = framework.meta ? chalk.dim(` (${framework.meta})`) : "";
|
|
332
|
-
const version = framework.version !== "unknown" ? ` ${framework.version}` : "";
|
|
333
|
-
console.log(chalk.green(" ✓ ") +
|
|
334
|
-
chalk.bold(frameworkName) +
|
|
335
|
-
ts +
|
|
336
|
-
meta +
|
|
337
|
-
chalk.dim(version));
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// Components
|
|
341
|
-
if (project.components.length > 0) {
|
|
342
|
-
for (const comp of project.components) {
|
|
343
|
-
const typeLabels = {
|
|
344
|
-
// JS frameworks
|
|
345
|
-
jsx: "component files",
|
|
346
|
-
tsx: "TypeScript components",
|
|
347
|
-
vue: "Vue components",
|
|
348
|
-
svelte: "Svelte components",
|
|
349
|
-
astro: "Astro components",
|
|
350
|
-
solid: "Solid components",
|
|
351
|
-
qwik: "Qwik components",
|
|
352
|
-
marko: "Marko components",
|
|
353
|
-
lit: "Lit elements",
|
|
354
|
-
fast: "FAST elements",
|
|
355
|
-
// Server-side templates
|
|
356
|
-
php: "PHP templates",
|
|
357
|
-
blade: "Blade templates",
|
|
358
|
-
erb: "ERB templates",
|
|
359
|
-
twig: "Twig templates",
|
|
360
|
-
html: "HTML templates",
|
|
361
|
-
njk: "Nunjucks templates",
|
|
362
|
-
razor: "Razor views",
|
|
363
|
-
hbs: "Handlebars templates",
|
|
364
|
-
mustache: "Mustache templates",
|
|
365
|
-
ejs: "EJS templates",
|
|
366
|
-
pug: "Pug templates",
|
|
367
|
-
liquid: "Liquid templates",
|
|
368
|
-
slim: "Slim templates",
|
|
369
|
-
haml: "Haml templates",
|
|
370
|
-
jinja: "Jinja templates",
|
|
371
|
-
django: "Django templates",
|
|
372
|
-
thymeleaf: "Thymeleaf templates",
|
|
373
|
-
freemarker: "Freemarker templates",
|
|
374
|
-
velocity: "Velocity templates",
|
|
375
|
-
"go-template": "Go templates",
|
|
376
|
-
edge: "Edge.js templates",
|
|
377
|
-
eta: "Eta templates",
|
|
378
|
-
heex: "HEEx templates",
|
|
379
|
-
xslt: "XSLT stylesheets",
|
|
380
|
-
// Static site generators
|
|
381
|
-
hugo: "Hugo layouts",
|
|
382
|
-
jekyll: "Jekyll layouts",
|
|
383
|
-
eleventy: "Eleventy templates",
|
|
384
|
-
shopify: "Shopify templates",
|
|
385
|
-
// Documentation
|
|
386
|
-
markdown: "Markdown files",
|
|
387
|
-
mdx: "MDX files",
|
|
388
|
-
asciidoc: "AsciiDoc files",
|
|
389
|
-
// Data templates
|
|
390
|
-
"yaml-template": "YAML templates",
|
|
391
|
-
"json-template": "JSON templates",
|
|
392
|
-
// Additional JS frameworks
|
|
393
|
-
angular: "Angular components",
|
|
394
|
-
stencil: "Stencil components",
|
|
395
|
-
alpine: "Alpine.js templates",
|
|
396
|
-
htmx: "HTMX templates",
|
|
397
|
-
// Graphics
|
|
398
|
-
svg: "SVG components",
|
|
399
|
-
};
|
|
400
|
-
const typeLabel = comp.type
|
|
401
|
-
? typeLabels[comp.type] || "template files"
|
|
402
|
-
: "component files";
|
|
403
|
-
console.log(chalk.green(" ✓ ") +
|
|
404
|
-
`${comp.fileCount} ${typeLabel} in ` +
|
|
405
|
-
chalk.cyan(comp.path));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
// Tokens
|
|
409
|
-
if (project.tokens.length > 0) {
|
|
410
|
-
for (const token of project.tokens) {
|
|
411
|
-
console.log(chalk.green(" ✓ ") + `${token.name}: ` + chalk.cyan(token.path));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// Storybook
|
|
415
|
-
if (project.storybook) {
|
|
416
|
-
const version = project.storybook.version
|
|
417
|
-
? ` (${project.storybook.version})`
|
|
418
|
-
: "";
|
|
419
|
-
console.log(chalk.green(" ✓ ") + `Storybook` + chalk.dim(version));
|
|
420
|
-
}
|
|
421
|
-
// Design system
|
|
422
|
-
if (project.designSystem) {
|
|
423
|
-
console.log(chalk.green(" ✓ ") +
|
|
424
|
-
`Design system: ` +
|
|
425
|
-
chalk.cyan(project.designSystem.package));
|
|
426
|
-
}
|
|
427
|
-
// Monorepo
|
|
428
|
-
if (project.monorepo) {
|
|
429
|
-
// Show basic monorepo info
|
|
430
|
-
console.log(chalk.green(" ✓ ") +
|
|
431
|
-
capitalize(project.monorepo.type) +
|
|
432
|
-
` monorepo (${project.monorepo.packages.length} packages)`);
|
|
433
|
-
// Show monorepo component paths if detected
|
|
434
|
-
const monorepoComponents = project.components.filter((c) => c.path.startsWith("packages/") ||
|
|
435
|
-
c.path.startsWith("apps/") ||
|
|
436
|
-
c.path.startsWith("libs/") ||
|
|
437
|
-
c.path.startsWith("modules/"));
|
|
438
|
-
if (monorepoComponents.length > 0) {
|
|
439
|
-
console.log(chalk.dim(" ") +
|
|
440
|
-
chalk.dim(`Scanning: ${monorepoComponents.map((c) => c.path).slice(0, 3).join(", ")}${monorepoComponents.length > 3 ? ` +${monorepoComponents.length - 3} more` : ""}`));
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
// Design system documentation tools
|
|
444
|
-
if (project.designSystemDocs) {
|
|
445
|
-
const typeNames = {
|
|
446
|
-
'zeroheight': 'Zeroheight',
|
|
447
|
-
'supernova': 'Supernova',
|
|
448
|
-
'specify': 'Specify',
|
|
449
|
-
'knapsack': 'Knapsack',
|
|
450
|
-
'framer': 'Framer',
|
|
451
|
-
'tokenforge': 'TokenForge',
|
|
452
|
-
'tokens-studio': 'Tokens Studio (Figma Tokens)',
|
|
453
|
-
};
|
|
454
|
-
const name = typeNames[project.designSystemDocs.type] || project.designSystemDocs.type;
|
|
455
|
-
const path = project.designSystemDocs.exportPath || project.designSystemDocs.configPath || '';
|
|
456
|
-
console.log(chalk.green(" ✓ ") +
|
|
457
|
-
chalk.bold(name) +
|
|
458
|
-
(path ? chalk.dim(` (${path})`) : ''));
|
|
459
|
-
}
|
|
460
|
-
// Nothing found
|
|
461
|
-
if (project.frameworks.length === 0 &&
|
|
462
|
-
project.components.length === 0 &&
|
|
463
|
-
project.tokens.length === 0 &&
|
|
464
|
-
!project.storybook &&
|
|
465
|
-
!project.designSystemDocs) {
|
|
466
|
-
console.log(chalk.yellow(" ⚠ ") + "No sources auto-detected");
|
|
467
|
-
console.log(chalk.dim(" You can manually configure sources in buoy.config.mjs"));
|
|
169
|
+
else {
|
|
170
|
+
console.log(` ${chalk.green("✓")} Hooks configured`);
|
|
171
|
+
results.hooksSetup = true;
|
|
468
172
|
}
|
|
173
|
+
// Summary
|
|
469
174
|
console.log("");
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const json = JSON.parse(content);
|
|
484
|
-
const format = detectFormat(json);
|
|
485
|
-
info(`Detected format: ${formatTokenFormat(format)}`);
|
|
486
|
-
tokens = parseTokenFile(content);
|
|
487
|
-
}
|
|
488
|
-
else if (ext === '.css') {
|
|
489
|
-
tokens = parseCssVariablesForInit(content, sourcePath);
|
|
175
|
+
console.log(chalk.dim(" ─".repeat(25)));
|
|
176
|
+
console.log("");
|
|
177
|
+
if (options.json) {
|
|
178
|
+
console.log(JSON.stringify(results, null, 2));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const allDone = results.configCreated && results.agentsSetup && results.hooksSetup;
|
|
182
|
+
if (allDone) {
|
|
183
|
+
console.log(chalk.green.bold(" ✓ Buoy is docked!"));
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log(chalk.dim(" Try:"));
|
|
186
|
+
console.log(` ${chalk.cyan("buoy show all")} # See your design system`);
|
|
187
|
+
console.log(` ${chalk.cyan("buoy show drift")} # Check for drift`);
|
|
490
188
|
}
|
|
491
189
|
else {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
190
|
+
console.log(chalk.yellow(" Partially docked"));
|
|
191
|
+
console.log("");
|
|
192
|
+
if (!results.configCreated)
|
|
193
|
+
console.log(` Run ${chalk.cyan("buoy dock config")}`);
|
|
194
|
+
if (!results.agentsSetup)
|
|
195
|
+
console.log(` Run ${chalk.cyan("buoy dock agents")}`);
|
|
196
|
+
if (!results.hooksSetup)
|
|
197
|
+
console.log(` Run ${chalk.cyan("buoy dock hooks")}`);
|
|
496
198
|
}
|
|
497
|
-
// Generate CSS output
|
|
498
|
-
const outputContent = generateCssFromTokens(tokens);
|
|
499
|
-
writeFileSync(outputPath, outputContent);
|
|
500
|
-
return { success: true, tokenCount: tokens.length };
|
|
501
|
-
}
|
|
502
|
-
catch {
|
|
503
|
-
return { success: false, tokenCount: 0 };
|
|
504
199
|
}
|
|
200
|
+
console.log("");
|
|
505
201
|
}
|
|
506
202
|
/**
|
|
507
|
-
*
|
|
203
|
+
* Config dock - creates buoy.config.mjs
|
|
508
204
|
*/
|
|
509
|
-
function
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
tokens.push({
|
|
517
|
-
id: `css:${filePath}:${name}`,
|
|
518
|
-
name: `--${name}`,
|
|
519
|
-
category: inferTokenCategory(name, value),
|
|
520
|
-
value: parseTokenValue(value),
|
|
521
|
-
source: { type: 'css', path: filePath },
|
|
522
|
-
aliases: [],
|
|
523
|
-
usedBy: [],
|
|
524
|
-
metadata: {},
|
|
525
|
-
scannedAt: new Date(),
|
|
526
|
-
});
|
|
205
|
+
async function runConfigDock(options) {
|
|
206
|
+
const cwd = process.cwd();
|
|
207
|
+
const configFilePath = resolve(cwd, "buoy.config.mjs");
|
|
208
|
+
if (existsSync(configFilePath) && !options.force) {
|
|
209
|
+
warning(`Config already exists at ${configFilePath}`);
|
|
210
|
+
info("Use --force to overwrite");
|
|
211
|
+
return;
|
|
527
212
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
213
|
+
let project;
|
|
214
|
+
if (options.skipDetect) {
|
|
215
|
+
const detector = new ProjectDetector(cwd);
|
|
216
|
+
project = {
|
|
217
|
+
name: (await detector.detect()).name,
|
|
218
|
+
root: cwd,
|
|
219
|
+
frameworks: [],
|
|
220
|
+
primaryFramework: null,
|
|
221
|
+
components: [],
|
|
222
|
+
tokens: [],
|
|
223
|
+
storybook: null,
|
|
224
|
+
designSystem: null,
|
|
225
|
+
monorepo: null,
|
|
226
|
+
designSystemDocs: null,
|
|
227
|
+
};
|
|
537
228
|
}
|
|
538
|
-
|
|
539
|
-
|
|
229
|
+
else {
|
|
230
|
+
const spin = ora("Scanning project...").start();
|
|
231
|
+
try {
|
|
232
|
+
const detector = new ProjectDetector(cwd);
|
|
233
|
+
project = await detector.detect();
|
|
234
|
+
spin.stop();
|
|
235
|
+
printDetectionResults(project);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
spin.fail("Detection failed");
|
|
239
|
+
error(err instanceof Error ? err.message : String(err));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
540
242
|
}
|
|
541
|
-
|
|
542
|
-
|
|
243
|
+
// Show frameworks and offer plugin install
|
|
244
|
+
const detectedFrameworks = await detectFrameworks(cwd);
|
|
245
|
+
if (detectedFrameworks.length > 0) {
|
|
246
|
+
await showFrameworksAndPlugins(detectedFrameworks, options.yes);
|
|
543
247
|
}
|
|
544
|
-
|
|
545
|
-
|
|
248
|
+
// Generate and write config
|
|
249
|
+
const content = generateConfig(project);
|
|
250
|
+
try {
|
|
251
|
+
writeFileSync(configFilePath, content, "utf-8");
|
|
252
|
+
success("Created buoy.config.mjs");
|
|
546
253
|
}
|
|
547
|
-
|
|
548
|
-
|
|
254
|
+
catch (err) {
|
|
255
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
256
|
+
process.exit(1);
|
|
549
257
|
}
|
|
550
|
-
return 'other';
|
|
551
258
|
}
|
|
552
259
|
/**
|
|
553
|
-
*
|
|
260
|
+
* Agents dock - sets up AI agents (skills + context + commands)
|
|
554
261
|
*/
|
|
555
|
-
function
|
|
556
|
-
|
|
557
|
-
|
|
262
|
+
async function runAgentsDock(options) {
|
|
263
|
+
const cwd = process.cwd();
|
|
264
|
+
if (options.json)
|
|
265
|
+
setJsonMode(true);
|
|
266
|
+
const spin = spinner("Setting up AI agents...");
|
|
267
|
+
try {
|
|
268
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
269
|
+
spin.text = "Scanning...";
|
|
270
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
271
|
+
const scanResult = await orchestrator.scan({ onProgress: (msg) => { spin.text = msg; } });
|
|
272
|
+
spin.text = "Analyzing...";
|
|
273
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
274
|
+
const engine = new SemanticDiffEngine();
|
|
275
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {
|
|
276
|
+
checkDeprecated: true,
|
|
277
|
+
checkNaming: true,
|
|
278
|
+
checkDocumentation: true,
|
|
279
|
+
});
|
|
280
|
+
spin.stop();
|
|
281
|
+
const results = {
|
|
282
|
+
skillCreated: false,
|
|
283
|
+
contextUpdated: false,
|
|
284
|
+
commandsInstalled: [],
|
|
285
|
+
stats: { tokens: 0, components: 0 },
|
|
286
|
+
};
|
|
287
|
+
// Create skill files
|
|
288
|
+
const skillPath = resolve(cwd, ".claude/skills/design-system");
|
|
289
|
+
if (options.dryRun) {
|
|
290
|
+
console.log(chalk.dim(" Would create: " + skillPath));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const exportService = new SkillExportService(projectName);
|
|
294
|
+
const skillResult = await exportService.export({ tokens: scanResult.tokens, components: scanResult.components, drifts: diffResult.drifts, projectName }, { sections: ["tokens", "components", "patterns", "anti-patterns"], outputPath: skillPath });
|
|
295
|
+
for (const file of skillResult.files) {
|
|
296
|
+
const dir = dirname(file.path);
|
|
297
|
+
if (!existsSync(dir))
|
|
298
|
+
mkdirSync(dir, { recursive: true });
|
|
299
|
+
writeFileSync(file.path, file.content);
|
|
300
|
+
}
|
|
301
|
+
results.skillCreated = true;
|
|
302
|
+
results.stats.tokens = skillResult.stats.tokens.total;
|
|
303
|
+
results.stats.components = skillResult.stats.components;
|
|
304
|
+
}
|
|
305
|
+
// Update CLAUDE.md
|
|
306
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
307
|
+
if (options.dryRun) {
|
|
308
|
+
console.log(chalk.dim(" Would update: CLAUDE.md"));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const contextResult = generateContext({ tokens: scanResult.tokens, components: scanResult.components, drifts: diffResult.drifts, projectName }, { detailLevel: "standard" });
|
|
312
|
+
let existingContent = "";
|
|
313
|
+
let hasSection = false;
|
|
314
|
+
if (existsSync(claudeMdPath)) {
|
|
315
|
+
existingContent = readFileSync(claudeMdPath, "utf-8");
|
|
316
|
+
hasSection = /^##?\s*[Dd]esign\s*[Ss]ystem/m.test(existingContent.replace(/```[\s\S]*?```/g, ""));
|
|
317
|
+
}
|
|
318
|
+
if (!hasSection) {
|
|
319
|
+
const section = `\n## Design System\n\n${contextResult.content}\n`;
|
|
320
|
+
if (existingContent) {
|
|
321
|
+
appendFileSync(claudeMdPath, section);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
writeFileSync(claudeMdPath, `# CLAUDE.md\n${section}`);
|
|
325
|
+
}
|
|
326
|
+
results.contextUpdated = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Install commands
|
|
330
|
+
if (!options.dryRun) {
|
|
331
|
+
const { installed } = installClaudeCommands(false);
|
|
332
|
+
results.commandsInstalled = installed;
|
|
333
|
+
}
|
|
334
|
+
if (options.json) {
|
|
335
|
+
console.log(JSON.stringify(results, null, 2));
|
|
336
|
+
}
|
|
337
|
+
else if (!options.dryRun) {
|
|
338
|
+
console.log("");
|
|
339
|
+
if (results.skillCreated)
|
|
340
|
+
console.log(` ${chalk.green("✓")} Created skill files`);
|
|
341
|
+
if (results.contextUpdated)
|
|
342
|
+
console.log(` ${chalk.green("✓")} Updated CLAUDE.md`);
|
|
343
|
+
if (results.commandsInstalled.length > 0) {
|
|
344
|
+
console.log(` ${chalk.green("✓")} Installed: ${results.commandsInstalled.map(c => `/${c}`).join(", ")}`);
|
|
345
|
+
}
|
|
346
|
+
console.log("");
|
|
347
|
+
}
|
|
558
348
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
349
|
+
catch (err) {
|
|
350
|
+
spin.stop();
|
|
351
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
352
|
+
process.exit(1);
|
|
562
353
|
}
|
|
563
|
-
return { type: 'raw', value };
|
|
564
354
|
}
|
|
565
355
|
/**
|
|
566
|
-
*
|
|
356
|
+
* Skills dock - creates skill files only
|
|
567
357
|
*/
|
|
568
|
-
function
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
358
|
+
async function runSkillsDock(options) {
|
|
359
|
+
const cwd = process.cwd();
|
|
360
|
+
if (options.json)
|
|
361
|
+
setJsonMode(true);
|
|
362
|
+
const spin = spinner("Creating skills...");
|
|
363
|
+
try {
|
|
364
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
365
|
+
spin.text = "Scanning...";
|
|
366
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
367
|
+
const scanResult = await orchestrator.scan({ onProgress: (msg) => { spin.text = msg; } });
|
|
368
|
+
spin.text = "Analyzing...";
|
|
369
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
370
|
+
const engine = new SemanticDiffEngine();
|
|
371
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {});
|
|
372
|
+
spin.stop();
|
|
373
|
+
let outputPath = options.output || ".claude/skills/design-system";
|
|
374
|
+
if (options.global)
|
|
375
|
+
outputPath = join(homedir(), ".claude", "skills", projectName);
|
|
376
|
+
outputPath = resolve(cwd, outputPath);
|
|
377
|
+
if (options.dryRun) {
|
|
378
|
+
console.log(chalk.dim(`Would create: ${outputPath}`));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const exportService = new SkillExportService(projectName);
|
|
382
|
+
const result = await exportService.export({ tokens: scanResult.tokens, components: scanResult.components, drifts: diffResult.drifts, projectName }, { sections: ["tokens", "components", "patterns", "anti-patterns"], outputPath });
|
|
383
|
+
for (const file of result.files) {
|
|
384
|
+
const dir = dirname(file.path);
|
|
385
|
+
if (!existsSync(dir))
|
|
386
|
+
mkdirSync(dir, { recursive: true });
|
|
387
|
+
writeFileSync(file.path, file.content);
|
|
388
|
+
}
|
|
389
|
+
if (options.json) {
|
|
390
|
+
console.log(JSON.stringify({ path: outputPath, stats: result.stats }, null, 2));
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
success(`Created skills at ${outputPath}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
spin.stop();
|
|
398
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
609
401
|
}
|
|
610
402
|
/**
|
|
611
|
-
*
|
|
403
|
+
* Context dock - generates CLAUDE.md section
|
|
612
404
|
*/
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
405
|
+
async function runContextDock(options) {
|
|
406
|
+
const cwd = process.cwd();
|
|
407
|
+
if (options.json)
|
|
408
|
+
setJsonMode(true);
|
|
409
|
+
const spin = spinner("Generating context...");
|
|
410
|
+
try {
|
|
411
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
412
|
+
spin.text = "Scanning...";
|
|
413
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
414
|
+
const scanResult = await orchestrator.scan();
|
|
415
|
+
spin.text = "Analyzing...";
|
|
416
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
417
|
+
const engine = new SemanticDiffEngine();
|
|
418
|
+
const diffResult = engine.analyzeComponents(scanResult.components, { availableTokens: scanResult.tokens });
|
|
419
|
+
spin.stop();
|
|
420
|
+
const contextResult = generateContext({ tokens: scanResult.tokens, components: scanResult.components, drifts: diffResult.drifts, projectName }, { detailLevel: options.detail || "standard" });
|
|
421
|
+
if (options.json) {
|
|
422
|
+
console.log(JSON.stringify({ content: contextResult.content, stats: contextResult.stats }, null, 2));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (options.stdout) {
|
|
426
|
+
console.log(contextResult.content);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const claudeMdPath = options.output || join(cwd, "CLAUDE.md");
|
|
430
|
+
const header = "## Design System\n\n";
|
|
431
|
+
if (existsSync(claudeMdPath)) {
|
|
432
|
+
const existing = readFileSync(claudeMdPath, "utf-8");
|
|
433
|
+
if (!existing.includes("## Design System")) {
|
|
434
|
+
appendFileSync(claudeMdPath, `\n${header}${contextResult.content}\n`);
|
|
435
|
+
success("Appended to CLAUDE.md");
|
|
633
436
|
}
|
|
634
437
|
else {
|
|
635
|
-
|
|
438
|
+
warning("CLAUDE.md already has Design System section");
|
|
636
439
|
}
|
|
637
|
-
}
|
|
638
|
-
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
writeFileSync(claudeMdPath, `# CLAUDE.md\n\n${header}${contextResult.content}\n`);
|
|
443
|
+
success("Created CLAUDE.md");
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
spin.stop();
|
|
448
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
639
451
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
// Check if config already exists
|
|
653
|
-
if (existsSync(configPath) && !options.force) {
|
|
654
|
-
warning(`Configuration already exists at ${configPath}`);
|
|
655
|
-
info("Use --force to overwrite");
|
|
656
|
-
return;
|
|
452
|
+
/**
|
|
453
|
+
* Hooks dock - sets up git hooks
|
|
454
|
+
*/
|
|
455
|
+
async function runHooksDock(options) {
|
|
456
|
+
const cwd = process.cwd();
|
|
457
|
+
console.log("");
|
|
458
|
+
const hookSystem = detectHookSystem(cwd);
|
|
459
|
+
if (hookSystem) {
|
|
460
|
+
info(`Detected: ${hookSystem}`);
|
|
461
|
+
const hookResult = setupHooks(cwd);
|
|
462
|
+
if (hookResult.success) {
|
|
463
|
+
success(hookResult.message);
|
|
657
464
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
components: [],
|
|
668
|
-
tokens: [],
|
|
669
|
-
storybook: null,
|
|
670
|
-
designSystem: null,
|
|
671
|
-
monorepo: null,
|
|
672
|
-
designSystemDocs: null,
|
|
673
|
-
};
|
|
465
|
+
else {
|
|
466
|
+
warning(hookResult.message);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
const standaloneResult = generateStandaloneHook(cwd);
|
|
471
|
+
if (standaloneResult.success) {
|
|
472
|
+
success(standaloneResult.message);
|
|
473
|
+
info("Copy to .git/hooks/pre-commit to activate");
|
|
674
474
|
}
|
|
675
475
|
else {
|
|
676
|
-
|
|
677
|
-
const spinner = ora("Scanning project...").start();
|
|
678
|
-
try {
|
|
679
|
-
const detector = new ProjectDetector(cwd);
|
|
680
|
-
project = await detector.detect();
|
|
681
|
-
if (options.name) {
|
|
682
|
-
project.name = options.name;
|
|
683
|
-
}
|
|
684
|
-
spinner.stop();
|
|
685
|
-
printDetectionResults(project);
|
|
686
|
-
}
|
|
687
|
-
catch (err) {
|
|
688
|
-
spinner.fail("Detection failed");
|
|
689
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
690
|
-
error(message);
|
|
691
|
-
process.exit(1);
|
|
692
|
-
}
|
|
476
|
+
warning(standaloneResult.message);
|
|
693
477
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
shouldImport = true;
|
|
718
|
-
}
|
|
719
|
-
else if (process.stdin.isTTY) {
|
|
720
|
-
shouldImport = await promptConfirm(` Import tokens from ${docName}?`, true);
|
|
721
|
-
}
|
|
722
|
-
if (shouldImport) {
|
|
723
|
-
const outputFile = 'design-tokens.css';
|
|
724
|
-
const importSpinner = ora(`Importing tokens from ${docName}...`).start();
|
|
725
|
-
const result = await importTokensFromSource(resolve(cwd, sourcePath), resolve(cwd, outputFile));
|
|
726
|
-
if (result.success) {
|
|
727
|
-
importSpinner.succeed(`Imported ${result.tokenCount} tokens to ${outputFile}`);
|
|
728
|
-
importedTokenFile = outputFile;
|
|
729
|
-
// Add to project tokens for config generation
|
|
730
|
-
project.tokens.push({
|
|
731
|
-
name: 'Design Tokens',
|
|
732
|
-
path: outputFile,
|
|
733
|
-
type: 'css',
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
importSpinner.warn('Could not parse tokens from source');
|
|
738
|
-
info(`You can manually import later with: ${chalk.cyan(`buoy import ${sourcePath}`)}`);
|
|
739
|
-
}
|
|
740
|
-
console.log("");
|
|
741
|
-
}
|
|
742
|
-
}
|
|
478
|
+
}
|
|
479
|
+
if (options.claude) {
|
|
480
|
+
console.log("");
|
|
481
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
482
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
483
|
+
const scanResult = await orchestrator.scan();
|
|
484
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
485
|
+
const engine = new SemanticDiffEngine();
|
|
486
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {});
|
|
487
|
+
const exportService = new SkillExportService(projectName);
|
|
488
|
+
const condensedContext = exportService.generateCondensedContext({
|
|
489
|
+
tokens: scanResult.tokens,
|
|
490
|
+
components: scanResult.components,
|
|
491
|
+
drifts: diffResult.drifts,
|
|
492
|
+
projectName,
|
|
493
|
+
});
|
|
494
|
+
const claudeDir = resolve(cwd, ".claude");
|
|
495
|
+
if (!existsSync(claudeDir))
|
|
496
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
497
|
+
writeFileSync(resolve(claudeDir, "buoy-context.md"), condensedContext);
|
|
498
|
+
const claudeResult = setupClaudeHooks(cwd);
|
|
499
|
+
if (claudeResult.success) {
|
|
500
|
+
success("Created Claude hooks");
|
|
743
501
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (detectedFrameworks.length > 0) {
|
|
747
|
-
// Separate built-in scanners from optional plugins
|
|
748
|
-
const builtIn = detectedFrameworks.filter((fw) => fw.scanner);
|
|
749
|
-
const optionalPlugins = detectedFrameworks.filter((fw) => fw.plugin && !fw.scanner);
|
|
750
|
-
// Show built-in scanners (no install needed)
|
|
751
|
-
if (builtIn.length > 0) {
|
|
752
|
-
console.log(chalk.bold(" Built-in Scanners") +
|
|
753
|
-
chalk.dim(" (no install needed)"));
|
|
754
|
-
console.log("");
|
|
755
|
-
for (const fw of builtIn) {
|
|
756
|
-
const scannerInfo = BUILTIN_SCANNERS[fw.scanner];
|
|
757
|
-
const scannerLabel = scannerInfo?.description || capitalize(fw.name);
|
|
758
|
-
console.log(` ${chalk.green("✓")} ${chalk.cyan.bold(scannerLabel)}`);
|
|
759
|
-
console.log(` ${chalk.dim(fw.evidence)}`);
|
|
760
|
-
console.log("");
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
// Show optional plugins (need install)
|
|
764
|
-
if (optionalPlugins.length > 0) {
|
|
765
|
-
console.log(chalk.bold(" Optional Plugins"));
|
|
766
|
-
console.log("");
|
|
767
|
-
for (const fw of optionalPlugins) {
|
|
768
|
-
const pluginInfo = PLUGIN_INFO[fw.plugin];
|
|
769
|
-
const pluginName = pluginInfo?.name || `@buoy-design/plugin-${fw.plugin}`;
|
|
770
|
-
console.log(` ${chalk.dim("┌")} ${chalk.cyan.bold(pluginName)}`);
|
|
771
|
-
console.log(` ${chalk.dim("│")}`);
|
|
772
|
-
// What was detected
|
|
773
|
-
const detectsLabel = pluginInfo?.detects || capitalize(fw.name);
|
|
774
|
-
console.log(` ${chalk.dim("│")} ${chalk.white("Detected:")} ${detectsLabel} ${chalk.dim(`(${fw.evidence.toLowerCase()})`)}`);
|
|
775
|
-
console.log(` ${chalk.dim("│")}`);
|
|
776
|
-
// What the plugin does
|
|
777
|
-
if (pluginInfo?.description) {
|
|
778
|
-
console.log(` ${chalk.dim("│")} ${chalk.dim(pluginInfo.description)}`);
|
|
779
|
-
}
|
|
780
|
-
console.log(` ${chalk.dim("└─")} ${chalk.dim(getPluginInstallCommand([fw.plugin]))}`);
|
|
781
|
-
console.log("");
|
|
782
|
-
}
|
|
783
|
-
const missingPlugins = optionalPlugins
|
|
784
|
-
.map((fw) => fw.plugin)
|
|
785
|
-
.filter((plugin, index, self) => self.indexOf(plugin) === index);
|
|
786
|
-
if (missingPlugins.length > 0) {
|
|
787
|
-
console.log(chalk.dim(" " + "─".repeat(65)));
|
|
788
|
-
console.log("");
|
|
789
|
-
console.log(chalk.bold(" Install all optional plugins:"));
|
|
790
|
-
console.log(` ${chalk.cyan(getPluginInstallCommand(missingPlugins))}`);
|
|
791
|
-
console.log("");
|
|
792
|
-
// Determine if we should install plugins
|
|
793
|
-
let shouldInstall = false;
|
|
794
|
-
if (options.yes) {
|
|
795
|
-
shouldInstall = true;
|
|
796
|
-
}
|
|
797
|
-
else if (options.install !== false) {
|
|
798
|
-
// Only prompt if --no-install was not passed and stdin is a TTY
|
|
799
|
-
if (process.stdin.isTTY) {
|
|
800
|
-
shouldInstall = await promptConfirm("Install optional plugins now?", true);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
if (shouldInstall) {
|
|
804
|
-
const { execSync } = await import("node:child_process");
|
|
805
|
-
console.log("");
|
|
806
|
-
console.log("Installing plugins...");
|
|
807
|
-
try {
|
|
808
|
-
execSync(getPluginInstallCommand(missingPlugins), {
|
|
809
|
-
stdio: "inherit",
|
|
810
|
-
});
|
|
811
|
-
success("Plugins installed successfully");
|
|
812
|
-
}
|
|
813
|
-
catch {
|
|
814
|
-
warning("Plugin installation failed. You can install manually with the command above.");
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
console.log("");
|
|
502
|
+
else {
|
|
503
|
+
warning(`Claude hooks failed: ${claudeResult.message}`);
|
|
820
504
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
505
|
+
}
|
|
506
|
+
console.log("");
|
|
507
|
+
}
|
|
508
|
+
// ============ Helpers ============
|
|
509
|
+
async function loadOrBuildConfig(cwd) {
|
|
510
|
+
const existingConfigPath = getConfigPath();
|
|
511
|
+
if (existingConfigPath) {
|
|
512
|
+
const result = await loadConfig();
|
|
513
|
+
return { config: result.config, projectName: result.config.project?.name || "design-system" };
|
|
514
|
+
}
|
|
515
|
+
const autoResult = await buildAutoConfig(cwd);
|
|
516
|
+
return { config: autoResult.config, projectName: autoResult.config.project?.name || "design-system" };
|
|
517
|
+
}
|
|
518
|
+
function installClaudeCommands(dryRun = false) {
|
|
519
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
520
|
+
const assetsDir = resolve(__dirname, "..", "..", "assets", "commands");
|
|
521
|
+
const installed = [];
|
|
522
|
+
const alreadyExisted = [];
|
|
523
|
+
if (!existsSync(assetsDir))
|
|
524
|
+
return { installed, alreadyExisted };
|
|
525
|
+
if (!dryRun && !existsSync(commandsDir))
|
|
526
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
527
|
+
for (const file of readdirSync(assetsDir).filter(f => f.endsWith(".md"))) {
|
|
528
|
+
const destPath = join(commandsDir, file);
|
|
529
|
+
if (existsSync(destPath)) {
|
|
530
|
+
alreadyExisted.push(file.replace(".md", ""));
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
if (!dryRun)
|
|
534
|
+
copyFileSync(join(assetsDir, file), destPath);
|
|
535
|
+
installed.push(file.replace(".md", ""));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return { installed, alreadyExisted };
|
|
539
|
+
}
|
|
540
|
+
function generateConfig(project) {
|
|
541
|
+
const lines = [];
|
|
542
|
+
const monorepoConfig = detectMonorepoConfig(project.root);
|
|
543
|
+
lines.push(`/** @type {import('@buoy-design/cli').BuoyConfig} */`);
|
|
544
|
+
lines.push(`export default {`);
|
|
545
|
+
lines.push(` project: { name: '${project.name}' },`);
|
|
546
|
+
lines.push(` sources: {`);
|
|
547
|
+
for (const framework of project.frameworks) {
|
|
548
|
+
const sourceKey = getSourceKey(framework.name);
|
|
549
|
+
if (sourceKey) {
|
|
550
|
+
const extensions = getExtensions(sourceKey, framework.typescript);
|
|
551
|
+
const defaultPatterns = extensions.map(ext => `src/**/*.${ext}`);
|
|
552
|
+
const patterns = monorepoConfig.type
|
|
553
|
+
? expandPatternsForMonorepo(defaultPatterns, monorepoConfig).allPatterns
|
|
554
|
+
: defaultPatterns;
|
|
555
|
+
lines.push(` ${sourceKey}: {`);
|
|
556
|
+
lines.push(` enabled: true,`);
|
|
557
|
+
lines.push(` include: [${patterns.map(p => `'${p}'`).join(", ")}],`);
|
|
558
|
+
lines.push(` exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],`);
|
|
559
|
+
lines.push(` },`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (project.tokens.length > 0) {
|
|
563
|
+
lines.push(` tokens: { enabled: true, files: [${project.tokens.map(t => `'${t.path}'`).join(", ")}] },`);
|
|
564
|
+
}
|
|
565
|
+
lines.push(` figma: { enabled: false },`);
|
|
566
|
+
lines.push(` },`);
|
|
567
|
+
lines.push(` output: { format: 'table', colors: true },`);
|
|
568
|
+
lines.push(`};`);
|
|
569
|
+
return lines.join("\n");
|
|
570
|
+
}
|
|
571
|
+
function getSourceKey(name) {
|
|
572
|
+
if (["react", "nextjs", "remix", "gatsby", "react-native", "expo", "preact", "solid"].includes(name))
|
|
573
|
+
return "react";
|
|
574
|
+
if (["vue", "nuxt"].includes(name))
|
|
575
|
+
return "vue";
|
|
576
|
+
if (["svelte", "sveltekit"].includes(name))
|
|
577
|
+
return "svelte";
|
|
578
|
+
if (name === "angular")
|
|
579
|
+
return "angular";
|
|
580
|
+
if (["lit", "stencil"].includes(name))
|
|
581
|
+
return "webcomponent";
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
function getExtensions(sourceKey, typescript) {
|
|
585
|
+
switch (sourceKey) {
|
|
586
|
+
case "vue": return ["vue"];
|
|
587
|
+
case "svelte": return ["svelte"];
|
|
588
|
+
case "angular": return ["component.ts"];
|
|
589
|
+
case "webcomponent": return ["ts"];
|
|
590
|
+
default: return typescript ? ["tsx", "jsx"] : ["jsx", "tsx"];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function printDetectionResults(project) {
|
|
594
|
+
console.log("");
|
|
595
|
+
console.log(chalk.bold(" Detected:"));
|
|
596
|
+
for (const fw of project.frameworks) {
|
|
597
|
+
console.log(chalk.green(" ✓ ") + chalk.bold(fw.name) + (fw.typescript ? " + TS" : ""));
|
|
598
|
+
}
|
|
599
|
+
for (const comp of project.components) {
|
|
600
|
+
console.log(chalk.green(" ✓ ") + `${comp.fileCount} files in ${chalk.cyan(comp.path)}`);
|
|
601
|
+
}
|
|
602
|
+
for (const token of project.tokens) {
|
|
603
|
+
console.log(chalk.green(" ✓ ") + `${token.name}: ${chalk.cyan(token.path)}`);
|
|
604
|
+
}
|
|
605
|
+
if (project.storybook)
|
|
606
|
+
console.log(chalk.green(" ✓ ") + "Storybook");
|
|
607
|
+
console.log("");
|
|
608
|
+
}
|
|
609
|
+
async function showFrameworksAndPlugins(frameworks, autoInstall) {
|
|
610
|
+
const builtIn = frameworks.filter(fw => fw.scanner);
|
|
611
|
+
const plugins = frameworks.filter(fw => fw.plugin && !fw.scanner);
|
|
612
|
+
if (builtIn.length > 0) {
|
|
613
|
+
console.log(chalk.bold(" Built-in Scanners"));
|
|
614
|
+
for (const fw of builtIn)
|
|
615
|
+
console.log(` ${chalk.green("✓")} ${chalk.cyan(fw.name)}`);
|
|
616
|
+
console.log("");
|
|
617
|
+
}
|
|
618
|
+
if (plugins.length > 0) {
|
|
619
|
+
const missing = plugins.map(fw => fw.plugin).filter((p, i, arr) => arr.indexOf(p) === i);
|
|
620
|
+
console.log(chalk.bold(" Optional Plugins"));
|
|
621
|
+
console.log(` ${chalk.dim(getPluginInstallCommand(missing))}`);
|
|
622
|
+
console.log("");
|
|
623
|
+
if (autoInstall) {
|
|
624
|
+
const { execSync } = await import("node:child_process");
|
|
625
|
+
try {
|
|
626
|
+
execSync(getPluginInstallCommand(missing), { stdio: "inherit" });
|
|
627
|
+
success("Plugins installed");
|
|
868
628
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
" to integrate with AI assistants");
|
|
872
|
-
if (!project.storybook) {
|
|
873
|
-
console.log("");
|
|
874
|
-
info(chalk.dim("Optional: Connect Figma by adding your API key to the config"));
|
|
629
|
+
catch {
|
|
630
|
+
warning("Plugin install failed");
|
|
875
631
|
}
|
|
876
632
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
636
|
+
if (!process.stdin.isTTY)
|
|
637
|
+
return defaultValue;
|
|
638
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
639
|
+
return new Promise(resolve => {
|
|
640
|
+
rl.question(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"} `, answer => {
|
|
641
|
+
rl.close();
|
|
642
|
+
const t = answer.trim().toLowerCase();
|
|
643
|
+
resolve(t === "" ? defaultValue : t === "y" || t === "yes");
|
|
644
|
+
});
|
|
882
645
|
});
|
|
883
|
-
return cmd;
|
|
884
646
|
}
|
|
885
647
|
//# sourceMappingURL=dock.js.map
|