@matteoaliano/forest-ui 0.2.0 → 0.2.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.
package/bin/sync.mjs CHANGED
@@ -3,38 +3,72 @@
3
3
  /**
4
4
  * forest-ui sync
5
5
  *
6
- * Copies FOREST_AI_GUIDELINES.md into the AI-tool config directories
6
+ * Copies guideline files into the AI-tool config directories
7
7
  * of the consuming project so coding assistants automatically follow
8
8
  * Forest UI conventions.
9
9
  *
10
10
  * Supported targets:
11
- * .claude/forest-ui.md — Claude Code
12
- * .cursor/rules/forest-ui.mdc — Cursor
11
+ * .claude/<name>.md — Claude Code
12
+ * .cursor/rules/<name>.mdc — Cursor
13
+ *
14
+ * Guideline categories:
15
+ * - FOREST_AI_GUIDELINES.md — Design system components, tokens, patterns
16
+ * - FOREST_DEV_GUIDELINES.md — Development best practices
17
+ * - themes/FOREST_THEME_<NAME>.md — Theme-specific guidelines (auto-discovered)
13
18
  */
14
19
 
15
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
16
- import { resolve, dirname } from "node:path";
20
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from "node:fs";
21
+ import { resolve, dirname, basename } from "node:path";
17
22
  import { fileURLToPath } from "node:url";
18
23
 
19
24
  const __filename = fileURLToPath(import.meta.url);
20
25
  const __dirname = dirname(__filename);
21
26
 
22
- // Locate the guidelines file shipped with the package
23
- const guidelinesPath = resolve(__dirname, "..", "FOREST_AI_GUIDELINES.md");
27
+ const guidelinesDir = resolve(__dirname, "..", "guidelines");
28
+ const themesDir = resolve(guidelinesDir, "themes");
24
29
 
25
- if (!existsSync(guidelinesPath)) {
26
- console.error("❌ Could not find FOREST_AI_GUIDELINES.md in the forest-ui package.");
27
- process.exit(1);
28
- }
30
+ // Static guideline entries
31
+ const staticEntries = [
32
+ {
33
+ source: resolve(guidelinesDir, "FOREST_AI_GUIDELINES.md"),
34
+ claudeName: "forest-ui.md",
35
+ cursorName: "forest-ui.mdc",
36
+ cursorDescription: "Forest UI Design System guidelines — components, tokens, and patterns",
37
+ },
38
+ {
39
+ source: resolve(guidelinesDir, "FOREST_DEV_GUIDELINES.md"),
40
+ claudeName: "forest-dev.md",
41
+ cursorName: "forest-dev.mdc",
42
+ cursorDescription: "Forest UI development best practices",
43
+ },
44
+ ];
45
+
46
+ // Auto-discover theme files from guidelines/themes/
47
+ function discoverThemeEntries() {
48
+ if (!existsSync(themesDir)) return [];
49
+
50
+ return readdirSync(themesDir)
51
+ .filter((f) => /^FOREST_THEME_\w+\.md$/.test(f))
52
+ .map((f) => {
53
+ const themeName = f
54
+ .replace(/^FOREST_THEME_/, "")
55
+ .replace(/\.md$/, "")
56
+ .toLowerCase();
29
57
 
30
- const guidelines = readFileSync(guidelinesPath, "utf-8");
58
+ return {
59
+ source: resolve(themesDir, f),
60
+ claudeName: `forest-theme-${themeName}.md`,
61
+ cursorName: `forest-theme-${themeName}.mdc`,
62
+ cursorDescription: `Forest UI theme guidelines — ${themeName}`,
63
+ };
64
+ });
65
+ }
31
66
 
32
67
  // Find the consuming project root (walk up until we find package.json)
33
68
  function findProjectRoot(startDir) {
34
69
  let dir = startDir;
35
70
  while (dir !== dirname(dir)) {
36
71
  if (existsSync(resolve(dir, "package.json"))) {
37
- // Skip if this is the forest-ui package itself
38
72
  try {
39
73
  const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
40
74
  if (pkg.name !== "@matteoaliano/forest-ui") return dir;
@@ -48,44 +82,51 @@ function findProjectRoot(startDir) {
48
82
  }
49
83
 
50
84
  const projectRoot = findProjectRoot(process.cwd());
51
-
52
- // Define sync targets
53
- const targets = [
54
- {
55
- name: "Claude Code",
56
- dir: resolve(projectRoot, ".claude"),
57
- file: "forest-ui.md",
58
- },
59
- {
60
- name: "Cursor",
61
- dir: resolve(projectRoot, ".cursor", "rules"),
62
- file: "forest-ui.mdc",
63
- },
64
- ];
85
+ const entries = [...staticEntries, ...discoverThemeEntries()];
65
86
 
66
87
  console.log(`\n🌲 Forest UI — Syncing AI guidelines\n`);
67
- console.log(` Project root: ${projectRoot}\n`);
88
+ console.log(` Project root: ${projectRoot}`);
89
+ console.log(` Found ${entries.length} guideline file(s)\n`);
68
90
 
69
91
  let synced = 0;
92
+ let total = 0;
93
+
94
+ for (const entry of entries) {
95
+ if (!existsSync(entry.source)) {
96
+ console.log(` ⚠️ ${basename(entry.source)} — not found, skipping`);
97
+ continue;
98
+ }
70
99
 
71
- for (const target of targets) {
100
+ const content = readFileSync(entry.source, "utf-8");
101
+
102
+ // Claude Code target
103
+ const claudeDir = resolve(projectRoot, ".claude");
104
+ const claudePath = resolve(claudeDir, entry.claudeName);
105
+ total++;
72
106
  try {
73
- mkdirSync(target.dir, { recursive: true });
74
-
75
- // For Cursor .mdc files, prepend frontmatter
76
- let content = guidelines;
77
- if (target.file.endsWith(".mdc")) {
78
- content =
79
- `---\ndescription: Forest UI Design System guidelines — components, tokens, and patterns\nglobs: **/*.{ts,tsx,js,jsx}\nalwaysApply: false\n---\n\n` +
80
- guidelines;
81
- }
107
+ mkdirSync(claudeDir, { recursive: true });
108
+ writeFileSync(claudePath, content, "utf-8");
109
+ console.log(` ✅ Claude Code .claude/${entry.claudeName}`);
110
+ synced++;
111
+ } catch (err) {
112
+ console.log(` ⚠️ Claude Code — .claude/${entry.claudeName} skipped (${err.message})`);
113
+ }
82
114
 
83
- writeFileSync(resolve(target.dir, target.file), content, "utf-8");
84
- console.log(` ✅ ${target.name} → ${target.dir}/${target.file}`);
115
+ // Cursor target
116
+ const cursorDir = resolve(projectRoot, ".cursor", "rules");
117
+ const cursorPath = resolve(cursorDir, entry.cursorName);
118
+ total++;
119
+ try {
120
+ mkdirSync(cursorDir, { recursive: true });
121
+ const cursorContent =
122
+ `---\ndescription: ${entry.cursorDescription}\nglobs: **/*.{ts,tsx,js,jsx}\nalwaysApply: false\n---\n\n` +
123
+ content;
124
+ writeFileSync(cursorPath, cursorContent, "utf-8");
125
+ console.log(` ✅ Cursor → .cursor/rules/${entry.cursorName}`);
85
126
  synced++;
86
127
  } catch (err) {
87
- console.log(` ⚠️ ${target.name} skipped (${err.message})`);
128
+ console.log(` ⚠️ Cursor — .cursor/rules/${entry.cursorName} skipped (${err.message})`);
88
129
  }
89
130
  }
90
131
 
91
- console.log(`\n Synced to ${synced}/${targets.length} targets.\n`);
132
+ console.log(`\n Synced ${synced}/${total} targets.\n`);