@matteoaliano/forest-ui 0.4.0 → 0.4.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,60 +3,17 @@
3
3
  /**
4
4
  * forest-ui sync
5
5
  *
6
- * Copies guideline files into the AI-tool config directories
7
- * of the consuming project so coding assistants automatically follow
8
- * Forest UI conventions.
9
- *
10
- * Supported targets:
11
- * .claude/<name>.md — Claude Code
12
- * .cursor/rules/<name>.mdc — Cursor
13
- * .gemini/<name>.md — Gemini CLI
14
- * .antigravity/rules.md — Antigravity
15
- *
16
- * Guideline categories:
17
- * - FOREST_UI_GUIDELINES.md — Design system components, tokens, patterns
18
- * - FOREST_FE_GUIDELINES.md — Next.js frontend best practices
19
- * - FOREST_BE_GUIDELINES.md — FastAPI backend best practices
6
+ * Copies skill folders into the consuming project's .claude/skills/
7
+ * directory so Claude Code automatically discovers Forest UI preset rules.
20
8
  */
21
9
 
22
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, cpSync } from "node:fs";
23
- import { resolve, dirname, basename } from "node:path";
10
+ import { readFileSync, mkdirSync, existsSync, readdirSync, cpSync, rmSync } from "node:fs";
11
+ import { resolve, dirname } from "node:path";
24
12
  import { fileURLToPath } from "node:url";
25
13
 
26
- const __filename = fileURLToPath(import.meta.url);
27
- const __dirname = dirname(__filename);
28
-
29
- const pkgPath = resolve(__dirname, "..", "package.json");
30
- const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
31
-
32
- const guidelinesDir = resolve(__dirname, "..", "guidelines");
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
15
  const skillsDir = resolve(__dirname, "..", "skills");
34
16
 
35
- // Static guideline entries
36
- const staticEntries = [
37
- {
38
- source: resolve(guidelinesDir, "FOREST_UI_GUIDELINES.md"),
39
- claudeName: "forest-ui.md",
40
- geminiName: "forest-ui.md",
41
- cursorName: "forest-ui.mdc",
42
- cursorDescription: "Forest UI Design System guidelines — components, tokens, and patterns",
43
- },
44
- {
45
- source: resolve(guidelinesDir, "FOREST_FE_GUIDELINES.md"),
46
- claudeName: "forest-fe.md",
47
- geminiName: "forest-fe.md",
48
- cursorName: "forest-fe.mdc",
49
- cursorDescription: "Next.js frontend best practices",
50
- },
51
- {
52
- source: resolve(guidelinesDir, "FOREST_BE_GUIDELINES.md"),
53
- claudeName: "forest-be.md",
54
- geminiName: "forest-be.md",
55
- cursorName: "forest-be.mdc",
56
- cursorDescription: "FastAPI backend requirements and best practices",
57
- },
58
- ];
59
-
60
17
  // Find the consuming project root (walk up until we find package.json)
61
18
  function findProjectRoot(startDir) {
62
19
  let dir = startDir;
@@ -75,122 +32,65 @@ function findProjectRoot(startDir) {
75
32
  }
76
33
 
77
34
  const projectRoot = findProjectRoot(process.cwd());
78
- const entries = [...staticEntries];
79
35
 
80
- console.log(`\n🌲 Forest UI — Syncing AI guidelines\n`);
81
- console.log(` Project root: ${projectRoot}`);
82
- console.log(` Found ${entries.length} guideline file(s)\n`);
36
+ console.log(`\n🌲 Forest UI — Syncing skills\n`);
37
+ console.log(` Project root: ${projectRoot}\n`);
83
38
 
84
- let synced = 0;
85
- let total = 0;
39
+ // Clean up legacy guideline files from pre-v0.4.1
40
+ const legacyPaths = [
41
+ resolve(projectRoot, ".claude", "forest-ui.md"),
42
+ resolve(projectRoot, ".cursor", "rules", "forest-ui.mdc"),
43
+ resolve(projectRoot, ".gemini", "forest-ui.md"),
44
+ resolve(projectRoot, ".antigravity", "rules.md"),
45
+ ];
86
46
 
87
- for (const entry of entries) {
88
- if (!existsSync(entry.source)) {
89
- console.log(` ⚠️ ${basename(entry.source)} — not found, skipping`);
90
- continue;
47
+ let cleaned = 0;
48
+ for (const legacyPath of legacyPaths) {
49
+ if (existsSync(legacyPath)) {
50
+ try {
51
+ rmSync(legacyPath);
52
+ const rel = legacyPath.replace(projectRoot + "/", "");
53
+ console.log(` 🧹 Removed legacy ${rel}`);
54
+ cleaned++;
55
+ } catch (err) {
56
+ const rel = legacyPath.replace(projectRoot + "/", "");
57
+ console.log(` ⚠️ Could not remove ${rel} (${err.message})`);
58
+ }
91
59
  }
60
+ }
61
+ if (cleaned > 0) console.log();
92
62
 
93
- const raw = readFileSync(entry.source, "utf-8");
94
- const content = raw.replace(
95
- />\s*\*\*forest-ui v[^*]*\*\*/,
96
- `> **forest-ui v${pkgVersion}**`,
97
- );
63
+ let synced = 0;
64
+ let total = 0;
98
65
 
99
- // Claude Code target
100
- const claudeDir = resolve(projectRoot, ".claude");
101
- const claudePath = resolve(claudeDir, entry.claudeName);
102
- total++;
103
- try {
104
- mkdirSync(claudeDir, { recursive: true });
105
- writeFileSync(claudePath, content, "utf-8");
106
- console.log(` ✅ Claude Code → .claude/${entry.claudeName}`);
107
- synced++;
108
- } catch (err) {
109
- console.log(` ⚠️ Claude Code — .claude/${entry.claudeName} skipped (${err.message})`);
110
- }
66
+ if (!existsSync(skillsDir)) {
67
+ console.log(` ⚠️ No skills directory found, nothing to sync.\n`);
68
+ process.exit(0);
69
+ }
111
70
 
112
- // Gemini target
113
- const geminiDir = resolve(projectRoot, ".gemini");
114
- const geminiPath = resolve(geminiDir, entry.geminiName);
115
- total++;
116
- try {
117
- mkdirSync(geminiDir, { recursive: true });
118
- writeFileSync(geminiPath, content, "utf-8");
119
- console.log(` ✅ Gemini → .gemini/${entry.geminiName}`);
120
-
121
- // Update .gemini/GEMINI.md to import all synced files
122
- const geminiMainPath = resolve(geminiDir, "GEMINI.md");
123
- const geminiMainContent = entries
124
- .map(e => `@${e.geminiName}`)
125
- .join("\n");
126
- writeFileSync(geminiMainPath, `# Forest UI Guidelines\n\n${geminiMainContent}\n`, "utf-8");
127
-
128
- synced++;
129
- } catch (err) {
130
- console.log(` ⚠️ Gemini — .gemini/${entry.geminiName} skipped (${err.message})`);
131
- }
71
+ const skillFolders = readdirSync(skillsDir, { withFileTypes: true })
72
+ .filter((d) => d.isDirectory())
73
+ .map((d) => d.name);
132
74
 
133
- // Antigravity target
134
- const antiDir = resolve(projectRoot, ".antigravity");
135
- const antiPath = resolve(antiDir, "rules.md");
136
- total++;
137
- try {
138
- mkdirSync(antiDir, { recursive: true });
139
- // Concatenate all content into rules.md or use imports if supported.
140
- // Using imports to keep it clean, pointing to the .gemini folder.
141
- const antiContent = entries
142
- .map(e => `@../.gemini/${e.geminiName}`)
143
- .join("\n");
144
- writeFileSync(antiPath, `# Forest UI — Antigravity Rules\n\n${antiContent}\n`, "utf-8");
145
- console.log(` ✅ Antigravity → .antigravity/rules.md`);
146
- synced++;
147
- } catch (err) {
148
- console.log(` ⚠️ Antigravity — .antigravity/rules.md skipped (${err.message})`);
149
- }
75
+ if (skillFolders.length === 0) {
76
+ console.log(` ⚠️ No skill folders found, nothing to sync.\n`);
77
+ process.exit(0);
78
+ }
150
79
 
151
- // Cursor target
152
- const cursorDir = resolve(projectRoot, ".cursor", "rules");
153
- const cursorPath = resolve(cursorDir, entry.cursorName);
80
+ const claudeSkillsDir = resolve(projectRoot, ".claude", "skills");
81
+
82
+ for (const folder of skillFolders) {
83
+ const src = resolve(skillsDir, folder);
84
+ const dest = resolve(claudeSkillsDir, folder);
154
85
  total++;
155
86
  try {
156
- mkdirSync(cursorDir, { recursive: true });
157
- const cursorContent =
158
- `---\ndescription: ${entry.cursorDescription}\nglobs: **/*.{ts,tsx,js,jsx}\nalwaysApply: false\n---\n\n` +
159
- content;
160
- writeFileSync(cursorPath, cursorContent, "utf-8");
161
- console.log(` ✅ Cursor → .cursor/rules/${entry.cursorName}`);
87
+ mkdirSync(dest, { recursive: true });
88
+ cpSync(src, dest, { recursive: true });
89
+ console.log(` ✅ .claude/skills/${folder}/`);
162
90
  synced++;
163
91
  } catch (err) {
164
- console.log(` ⚠️ Cursor — .cursor/rules/${entry.cursorName} skipped (${err.message})`);
165
- }
166
- }
167
-
168
- // ─── Claude Code Skills ────────────────────────────────────────────────
169
- // Copy skill folders into .claude/skills/ for auto-discovery
170
-
171
- if (existsSync(skillsDir)) {
172
- const skillFolders = readdirSync(skillsDir, { withFileTypes: true })
173
- .filter((d) => d.isDirectory())
174
- .map((d) => d.name);
175
-
176
- if (skillFolders.length > 0) {
177
- console.log(` --- Claude Code Skills ---`);
178
- const claudeSkillsDir = resolve(projectRoot, ".claude", "skills");
179
-
180
- for (const folder of skillFolders) {
181
- const src = resolve(skillsDir, folder);
182
- const dest = resolve(claudeSkillsDir, folder);
183
- total++;
184
- try {
185
- mkdirSync(dest, { recursive: true });
186
- cpSync(src, dest, { recursive: true });
187
- console.log(` ✅ Skill → .claude/skills/${folder}/`);
188
- synced++;
189
- } catch (err) {
190
- console.log(` ⚠️ Skill — .claude/skills/${folder}/ skipped (${err.message})`);
191
- }
192
- }
92
+ console.log(` ⚠️ .claude/skills/${folder}/ skipped (${err.message})`);
193
93
  }
194
94
  }
195
95
 
196
- console.log(`\n Synced ${synced}/${total} targets.\n`);
96
+ console.log(`\n Synced ${synced}/${total} skills.\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matteoaliano/forest-ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Forest Design System — themed MUI components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -26,16 +26,14 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "bin",
29
- "guidelines",
30
29
  "skills",
31
30
  "fonts"
32
31
  ],
33
32
  "scripts": {
34
- "presync": "node bin/stamp-version.mjs",
35
33
  "sync": "node bin/sync.mjs",
36
34
  "lint": "eslint src --ext .ts,.tsx",
37
35
  "build": "tsup",
38
- "prepublishOnly": "node bin/stamp-version.mjs && tsup && node bin/sync.mjs",
36
+ "prepublishOnly": "tsup && node bin/sync.mjs",
39
37
  "dev": "tsup --watch",
40
38
  "test": "vitest",
41
39
  "test:coverage": "vitest --coverage",
@@ -3,7 +3,7 @@ name: forest-agency
3
3
  description: Forest UI Design System rules for the Agency (charcoal/red) preset. Enforces correct imports, component usage, and theming with @matteoaliano/forest-ui. Use when the project uses forest-ui, forest-agency preset, or when user builds UI components in a forest-ui project. Triggers on "forest", "forest-ui", "forest agency", "@matteoaliano/forest-ui".
4
4
  metadata:
5
5
  author: Forest Design System
6
- version: 0.3.5
6
+ version: 0.4.1
7
7
  ---
8
8
 
9
9
  # Forest UI — Agency Preset
@@ -35,29 +35,6 @@ function App() {
35
35
  }
36
36
  ```
37
37
 
38
- ## Preset Identity
39
-
40
- **Atmosphere:** Bold, authoritative, and editorial — commanding yet refined.
41
-
42
- | Role | Color | Hex |
43
- |------|-------|-----|
44
- | Primary (brand) | Commanding Charcoal | `#09090b` |
45
- | Secondary (brand) | Deep Onyx | `#18181b` |
46
- | Accent | Signal Red | `#FF2821` |
47
- | Background | Warm Parchment | `#faf9f8` |
48
- | Surface | Pure White | `#ffffff` |
49
- | Text primary | Near-Black Ink | `#18181b` |
50
- | Text secondary | Steel Gray | `#3f3f46` |
51
- | Border primary | Silver Mist | `#d8d8dc` |
52
- | Success | Verdant | `#079455` |
53
- | Warning | Amber | `#dc6803` |
54
- | Error | Coral | `#d92d20` |
55
- | Info | Sky | `#1570ef` |
56
-
57
- **Corner radius:** Sharp and architectural (4px for buttons/inputs, up to 12px for cards/modals).
58
- **Font:** Aeonik (geometric sans-serif). Consumers must load it.
59
- **Button height:** Compact 32px.
60
-
61
38
  ## Available Components
62
39
 
63
40
  **Inputs:** Button, ButtonGroup, TextField, Select + MenuItem, MultiSelect, Checkbox, RadioGroup + Radio, Switch, ToggleButton + ToggleButtonGroup, Fab, IconButton, Autocomplete, Search, DatePicker
@@ -74,6 +51,13 @@ function App() {
74
51
 
75
52
  See `references/components.md` for full API details and `references/patterns.md` for code examples.
76
53
 
54
+ ## Tooltip vs Popover
55
+
56
+ - **Tooltip** — Use for **text-only hints**. The Tooltip has a dark (black) background and is meant for short, plain-text labels or descriptions. Do not nest rich content inside a Tooltip.
57
+ - **Popover** — Use when you need to display **rich or interactive content** such as Chips, lists, buttons, or any nested components. Popover renders in a neutral surface container that supports arbitrary children.
58
+
59
+ **Rule of thumb:** If the overlay content is just a string, use `<Tooltip>`. If it contains components, use `<Popover>`.
60
+
77
61
  ## Common Anti-Patterns
78
62
 
79
63
  1. **Importing from `@mui/material`** instead of `@matteoaliano/forest-ui`
@@ -3,7 +3,7 @@ name: forest-external
3
3
  description: Forest UI Design System rules for the External (violet) preset. Enforces correct imports, component usage, and theming with @matteoaliano/forest-ui. Use when the project uses forest-ui, forest-external preset, or when user builds UI components in a forest-ui project. Triggers on "forest", "forest-ui", "forest external", "@matteoaliano/forest-ui".
4
4
  metadata:
5
5
  author: Forest Design System
6
- version: 0.3.5
6
+ version: 0.4.1
7
7
  ---
8
8
 
9
9
  # Forest UI — External Preset
@@ -35,31 +35,6 @@ function App() {
35
35
  }
36
36
  ```
37
37
 
38
- ## Preset Identity
39
-
40
- **Atmosphere:** Vibrant, creative, and confidently modern — electric yet composed.
41
-
42
- | Role | Color | Hex |
43
- |------|-------|-----|
44
- | Primary (brand) | Electric Violet | `#9D5FFF` |
45
- | Primary hover | Deep Amethyst | `#7c4de6` |
46
- | Brand depth | Royal Violet | `#5b3acc` |
47
- | Brand tint | Soft Lavender | `#f5f0ff` |
48
- | Background | Warm Parchment | `#faf9f8` |
49
- | Surface | Pure White | `#ffffff` |
50
- | Text primary | Near-Black Ink | `#18181b` |
51
- | Text secondary | Steel Gray | `#3f3f46` |
52
- | Border primary | Silver Mist | `#d8d8dc` |
53
- | Border brand | Violet Frost | `#b090ff` |
54
- | Success | Verdant | `#079455` |
55
- | Warning | Amber | `#dc6803` |
56
- | Error | Coral | `#d92d20` |
57
- | Info | Sky | `#1570ef` |
58
-
59
- **Corner radius:** Sharp and contemporary (4px for buttons/inputs, up to 12px for cards/modals).
60
- **Font:** Aeonik (geometric sans-serif). Consumers must load it.
61
- **Button height:** Compact 32px.
62
-
63
38
  ## Available Components
64
39
 
65
40
  **Inputs:** Button, ButtonGroup, TextField, Select + MenuItem, MultiSelect, Checkbox, RadioGroup + Radio, Switch, ToggleButton + ToggleButtonGroup, Fab, IconButton, Autocomplete, Search, DatePicker
@@ -76,6 +51,13 @@ function App() {
76
51
 
77
52
  See `references/components.md` for full API details and `references/patterns.md` for code examples.
78
53
 
54
+ ## Tooltip vs Popover
55
+
56
+ - **Tooltip** — Use for **text-only hints**. The Tooltip has a dark (black) background and is meant for short, plain-text labels or descriptions. Do not nest rich content inside a Tooltip.
57
+ - **Popover** — Use when you need to display **rich or interactive content** such as Chips, lists, buttons, or any nested components. Popover renders in a neutral surface container that supports arbitrary children.
58
+
59
+ **Rule of thumb:** If the overlay content is just a string, use `<Tooltip>`. If it contains components, use `<Popover>`.
60
+
79
61
  ## Common Anti-Patterns
80
62
 
81
63
  1. **Importing from `@mui/material`** instead of `@matteoaliano/forest-ui`
@@ -3,7 +3,7 @@ name: forest-internal
3
3
  description: Forest UI Design System rules for the Internal (magenta) preset. Enforces correct imports, component usage, and theming with @matteoaliano/forest-ui. Use when the project uses forest-ui, forest-internal preset, or when user builds UI components in a forest-ui project. Triggers on "forest", "forest-ui", "forest internal", "@matteoaliano/forest-ui".
4
4
  metadata:
5
5
  author: Forest Design System
6
- version: 0.3.5
6
+ version: 0.4.1
7
7
  ---
8
8
 
9
9
  # Forest UI — Internal Preset
@@ -35,31 +35,6 @@ function App() {
35
35
  }
36
36
  ```
37
37
 
38
- ## Preset Identity
39
-
40
- **Atmosphere:** Playful, energetic, and unapologetically expressive — vibrant yet functional.
41
-
42
- | Role | Color | Hex |
43
- |------|-------|-----|
44
- | Primary (brand) | Hot Magenta | `#FF78F3` |
45
- | Primary hover | Vivid Fuchsia | `#e650d4` |
46
- | Brand depth | Deep Orchid | `#cc28b5` |
47
- | Brand tint | Blush Mist | `#fff0fa` |
48
- | Background | Warm Parchment | `#faf9f8` |
49
- | Surface | Pure White | `#ffffff` |
50
- | Text primary | Near-Black Ink | `#18181b` |
51
- | Text secondary | Steel Gray | `#3f3f46` |
52
- | Border primary | Silver Mist | `#d8d8dc` |
53
- | Border brand | Pink Frost | `#ff90d8` |
54
- | Success | Verdant | `#079455` |
55
- | Warning | Amber | `#dc6803` |
56
- | Error | Coral | `#d92d20` |
57
- | Info | Sky | `#1570ef` |
58
-
59
- **Corner radius:** Sharp and geometric (4px for buttons/inputs, up to 12px for cards/modals).
60
- **Font:** Aeonik (geometric sans-serif). Consumers must load it.
61
- **Button height:** Compact 32px.
62
-
63
38
  ## Available Components
64
39
 
65
40
  **Inputs:** Button, ButtonGroup, TextField, Select + MenuItem, MultiSelect, Checkbox, RadioGroup + Radio, Switch, ToggleButton + ToggleButtonGroup, Fab, IconButton, Autocomplete, Search, DatePicker
@@ -76,6 +51,13 @@ function App() {
76
51
 
77
52
  See `references/components.md` for full API details and `references/patterns.md` for code examples.
78
53
 
54
+ ## Tooltip vs Popover
55
+
56
+ - **Tooltip** — Use for **text-only hints**. The Tooltip has a dark (black) background and is meant for short, plain-text labels or descriptions. Do not nest rich content inside a Tooltip.
57
+ - **Popover** — Use when you need to display **rich or interactive content** such as Chips, lists, buttons, or any nested components. Popover renders in a neutral surface container that supports arbitrary children.
58
+
59
+ **Rule of thumb:** If the overlay content is just a string, use `<Tooltip>`. If it contains components, use `<Popover>`.
60
+
79
61
  ## Common Anti-Patterns
80
62
 
81
63
  1. **Importing from `@mui/material`** instead of `@matteoaliano/forest-ui`
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Stamps the current package version into all guideline .md files.
5
- * Replaces lines matching `> **forest-ui v*.**` with the version from package.json.
6
- *
7
- * Run automatically before sync and publish via npm scripts.
8
- */
9
-
10
- import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs";
11
- import { resolve, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const pkgPath = resolve(__dirname, "..", "package.json");
16
- const version = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
17
-
18
- const guidelinesDir = resolve(__dirname, "..", "guidelines");
19
- const themesDir = resolve(guidelinesDir, "themes");
20
-
21
- const versionRe = />\s*\*\*forest-ui v[^*]*\*\*/;
22
- const replacement = `> **forest-ui v${version}**`;
23
-
24
- function stampFile(filePath) {
25
- const content = readFileSync(filePath, "utf-8");
26
- if (!versionRe.test(content)) return false;
27
- const updated = content.replace(versionRe, replacement);
28
- if (updated === content) return false;
29
- writeFileSync(filePath, updated, "utf-8");
30
- return true;
31
- }
32
-
33
- let stamped = 0;
34
-
35
- // Stamp top-level guideline files
36
- for (const f of readdirSync(guidelinesDir)) {
37
- if (!f.endsWith(".md")) continue;
38
- if (stampFile(resolve(guidelinesDir, f))) stamped++;
39
- }
40
-
41
- // Stamp theme files
42
- if (existsSync(themesDir)) {
43
- for (const f of readdirSync(themesDir)) {
44
- if (!f.endsWith(".md")) continue;
45
- if (stampFile(resolve(themesDir, f))) stamped++;
46
- }
47
- }
48
-
49
- if (stamped > 0) {
50
- console.log(` Stamped v${version} into ${stamped} guideline file(s)`);
51
- } else {
52
- console.log(` All guideline files already at v${version}`);
53
- }
@@ -1,90 +0,0 @@
1
- # FastAPI API Project: Requirements & Best Practices
2
-
3
- > **forest-ui v0.4.0**
4
-
5
- > **This file is synced by the `forest-ui` package.**
6
- > Run `npx forest-ui sync` to update it.
7
-
8
- ---
9
-
10
- **Stack:** FastAPI (with dependency injection), Python >=3.9 (via Poetry), PostgreSQL/SQL Server, Redis cache, OpenTelemetry, on-prem Kubernetes. Frontend on Vercel, authentication via Clerk, CI/CD with GitHub Actions, Docker images in Azure Container Registry (ACR) deployed to the cluster.
11
-
12
- FastAPI is a modern Python web framework built on Starlette and Pydantic. It offers an intuitive dependency-injection system for shared components like DB sessions or auth. Pydantic models automatically validate and parse request data. Organize code into an `app` Python package (with `app/main.py`, sub-packages for routers and a `dependencies.py`) so that imports work cleanly. Use **APIRouters** to group related endpoints and share tags/dependencies. Declare shared logic (auth checks, DB, etc.) as FastAPI dependencies (`Depends(...)`) in `app/dependencies.py` for reuse. Keep path operations concise by leveraging DI: for example, inject database sessions or authorization checks into endpoints rather than repeating code.
13
-
14
- ## Environment & Packaging (Poetry)
15
-
16
- - **Python & Poetry:** Use Python 3.9+. Manage dependencies with [Poetry](https://python-poetry.org) and define metadata in `pyproject.toml` (PEP 621). Poetry creates isolated virtualenvs and a lockfile for reproducible installs.
17
- - **Dependency Best Practices:** Initialize with `poetry init` to generate `pyproject.toml`. Add packages with `poetry add <pkg>` so they're recorded in both `pyproject.toml` and `poetry.lock`. Separate dev dependencies (e.g. pytest, linters) using `poetry add --dev`. Commit `poetry.lock` to source control to ensure all installs use exact versions. Regularly run `poetry update` and test to keep dependencies current. Use multi-stage Docker builds to minimize image size (install only production deps).
18
- - **Project Layout:** Follow a logical package structure: e.g.
19
- ```
20
- app/
21
- __init__.py
22
- main.py # app startup
23
- dependencies.py # shared Depends (DB sessions, auth checks, etc.)
24
- routers/ # API route modules
25
- __init__.py
26
- items.py
27
- users.py
28
- models/ # SQLModel/Pydantic models
29
- services/ # business logic if needed
30
- ```
31
- Keep code organized into modules; use semantic imports like `from app.routers import items`. Use `Annotated` dependencies where possible for clarity (e.g. `session: Session = Depends(get_session)`).
32
-
33
- ## Databases (PostgreSQL, SQL Server)
34
-
35
- - **SQL Databases:** FastAPI works with any SQL DB via SQLAlchemy/SQLModel. For example, [SQLModel](https://github.com/tiangolo/sqlmodel) (built on SQLAlchemy and Pydantic) can connect to PostgreSQL or SQL Server (via `pyodbc` or `pymssql`). In development you might use SQLite for simplicity, but **production** should use a dedicated DB server (e.g. PostgreSQL).
36
- - **Connection Handling:** Use one DB **session/connection per request** via a FastAPI dependency (`yield session` pattern). Configure SQLAlchemy connection pools to match workload (avoid re-creating engine per query). Use environment variables (or Kubernetes Secrets) for DB URLs/credentials. Use alembic or similar for migrations, rather than auto-creating tables in prod. Ensure proper indexes and foreign keys in schema.
37
- - **Security:** Never embed credentials in code. Use TLS to connect to the DB if supported. Sanitize inputs (Pydantic prevents SQL injection by type-checking and requiring parameters).
38
-
39
- ## Caching (Redis)
40
-
41
- - **Redis Usage:** Use Redis for caching or session data. Access via a Redis client (e.g. [redis-py](https://pypi.org/project/redis/) or `aioredis` for async). Store only non-sensitive, expirable data with appropriate TTLs. Abstract Redis access behind a service layer or dependency.
42
- - **Configuration:** Run Redis in a secured network (not public-facing). Require strong passwords and/or disable the default user. Enable TLS if possible to encrypt in-flight cache traffic. Set memory limits and eviction policies. Monitor usage and consider using Redis clusters for high availability.
43
- - **Best Practices:** Use Redis only as a cache; do not rely on it for permanent storage unless using Redis persistence properly. Use mature caching patterns (e.g. cache-aside). Flush or handle stale caches on deploy as needed.
44
-
45
- ## Observability (OpenTelemetry)
46
-
47
- - **Instrumentation:** Use [OpenTelemetry for Python](https://opentelemetry.io/docs/languages/python/) to collect traces and metrics. The Python SDK supports Python 3.9+. Install via pip: `pip install opentelemetry-api opentelemetry-sdk` and any needed instrumentations/exporters.
48
- - **Tracing & Metrics:** Auto-instrument FastAPI (there are middleware or instrumentation libs for ASGI frameworks) to trace HTTP requests. Instrument database calls (SQLAlchemy) and Redis calls. Export telemetry using OTLP or a vendor (Jaeger, Zipkin, Prometheus). Use trace data to diagnose latency; use metrics (like request rate, DB latency) to monitor health.
49
- - **Deployment:** Ensure an OpenTelemetry Collector or compatible backend is running to receive data. Tag telemetry with the service name and environment. Use semantic conventions for naming.
50
-
51
- ## Frontend (Vercel) & Authentication (Clerk)
52
-
53
- - **Vercel Deployment:** Host the frontend on Vercel (typically a Next.js or static SPA). Configure environment variables in Vercel for the API base URL. Use custom domains and automatic HTTPS. Restrict API CORS to the frontend domain using FastAPI's `CORSMiddleware` so only your front-end origin can access endpoints.
54
- - **Clerk Authentication:** Use [Clerk](https://clerk.com) to handle user signup/sign-in and sessions. Clerk provides UI components (for React/Next) and issues JWT tokens for authenticated users. In the FastAPI backend, verify these JWTs on each request. For example, use the [fastapi-clerk-auth](https://pypi.org/project/fastapi-clerk-auth/) middleware to automatically validate Clerk JWTs via Clerk's JWKS. This cleanly integrates with FastAPI's DI: endpoints requiring auth simply depend on the token guard.
55
- - **Auth Best Practices:** Enforce HTTPS so tokens can't be stolen in transit. Use Clerk's features (MFA, password policies, session limits) to harden accounts. Never expose backend secrets (like Clerk API keys) to the frontend. Use short-lived JWTs or session tokens.
56
-
57
- ## Security Considerations
58
-
59
- - **Transport & API Security:** Serve all traffic over HTTPS (TLS). Validate all inputs using Pydantic models to prevent injection or malformed data. Implement CORS rules to only allow trusted origins. Use FastAPI's security dependencies (e.g. `OAuth2PasswordBearer`, HTTP Bearer) to enforce auth on endpoints. Consider rate-limiting and IP blocking for brute-force protection.
60
- - **Kubernetes & Infrastructure:** Secure the on-prem cluster using Kubernetes best practices. Ensure the API server and etcd have TLS in-transit and (if needed) at-rest encryption. Use Role-Based Access Control (RBAC) to limit cluster operations. Enforce Pod Security Standards: run containers as non-root, use read-only filesystems, drop Linux capabilities. Define NetworkPolicies so Pods can only talk to required services (e.g. API Pod talks to DB/Redis, but not to other namespaces). Use secrets or a vault for all credentials (K8s Secrets should be encrypted at rest by enabling encryption). Audit and log all access (enable Kubernetes audit logs). Keep nodes patched and minimize host privileges.
61
- - **Container Image Security:** Scan images for vulnerabilities before deployment. Use minimal base images (e.g. Alpine or slim variants) to reduce attack surface. Do not run the application as root inside the container.
62
- - **Redis Security:** Only allow Redis access from internal service accounts. Use Redis AUTH and TLS. Restrict Redis CLI or admin interfaces.
63
- - **Clerk/Authentication:** Rely on standard protocols. Verify the `iat` and `exp` claims in JWTs to prevent replay. Synchronize clocks or add leeway if needed. Restrict token scopes so that clients only have the privileges they need.
64
- - **Secrets Management:** Store sensitive config (DB passwords, JWT keys) outside code. In Kubernetes, use Secrets and mount them into pods. In GitHub Actions, use encrypted secrets (do NOT hardcode credentials). Consider using OpenID Connect (OIDC) in GitHub Actions to avoid long-lived secrets when deploying to Azure.
65
- - **Audit & Compliance:** Log all significant events (logins, errors, deployments). Regularly review audit logs. Follow principle of least privilege for all services.
66
-
67
- ## Deployment & CI/CD Pipeline
68
-
69
- - **Docker & ACR:** Dockerize the app with a multi-stage `Dockerfile`: install Poetry and build the app, then copy only the `venv/lib/python...` and app code into a smaller final image. Tag images by commit/PR. Push images to Azure Container Registry in the same region as your cluster for low latency. Use a dedicated ACR per environment or namespace. In ACR, enable Azure RBAC: assign a service principal (for CI/CD) push rights and another identity pull rights. Disable the ACR admin user if not needed.
70
- - **GitHub Actions:** Automate CI/CD with Actions:
71
- - **CI (on push/PR):** Steps include `actions/checkout`, `actions/setup-python@v3` (choose Python 3.10+), `poetry install` (or `poetry install --no-dev` for quick CI), run linter (flake8/mypy) and tests. Cache the Poetry virtualenv or pip cache to speed up builds. Fail on test or lint errors.
72
- - **Build & Push (on main):** After CI passes, build the Docker image. Use `azure/login@v1` (or OIDC) to authenticate to Azure using a service principal stored in a GitHub secret. Then use `azure/CLI` or `docker/login-action` and `docker/build-push-action` to push the image to ACR. Tag images by semantic version or commit SHA.
73
- - **Deploy to K8s:** After pushing, use `azure/k8s-set-context` (or `kubectl` inside `azure/CLI`) to point to the on-prem cluster (this may require a kubeconfig stored securely). Apply updated Kubernetes manifests (using `kubectl apply` or `helm upgrade`). You can use Helm or Kustomize; store manifests alongside code or in a separate repo.
74
- - **Checks:** Include branch protection so only successful checks (lint/tests) allow merging to main. Use pull request previews (deploy to a test namespace) before production. Use Actions cache for Docker layers and Poetry packages.
75
-
76
- ## Kubernetes Deployment Patterns
77
-
78
- - **Manifests:** Define Deployments and Services in YAML. Always create a Service *before* pods that use it, so environment variables get injected correctly. Use DNS service names for inter-service calls. Avoid `hostPort`/`hostNetwork` (it ties pods to nodes); use a NodePort or Ingress for external access. For internal-only services, use ClusterIP or headless Services as needed.
79
- - **Labels & Configuration:** Use semantic, standardized labels (`app.kubernetes.io/name`, `component`, `tier`, etc.) to identify workloads. Store app config (like DB URLs or feature flags) in ConfigMaps/Secrets and mount them as env vars or files. Keep resource limits/requests set on pods. Use liveness/readiness probes to restart unhealthy pods. Consider HorizontalPodAutoscalers based on CPU/memory or custom metrics (if cluster has VPA/HPA enabled).
80
- - **Secrets:** Load Kubernetes Secrets (e.g. database credentials, JWT secret) into pods. Optionally use a tool like SealedSecrets or Vault for rotation.
81
- - **Ingress & Networking:** Expose the API via an Ingress or LoadBalancer with TLS. Ensure the TLS certificate is valid (use internal CA or Let's Encrypt).
82
- - **Monitoring:** Run a Prometheus/Alertmanager stack (or other monitoring) in the cluster. Configure probes and health endpoints for FastAPI. Forward logs to a central system (ELK/EFK or Grafana Loki). Use OpenTelemetry collector if needed.
83
-
84
- ## Final Checklist
85
-
86
- - **Code Quality:** Follow PEP 8 and Pylint/myPy linting. Ensure unit and integration tests cover key logic. Use `@pytest` with the TestClient for endpoint tests.
87
- - **Dependency Audit:** Before each release, run a security scan on Python packages (e.g. `poetry audit` or `safety`) and on container images. Update vulnerable dependencies promptly.
88
- - **Review Configuration:** Verify all environment variables and secrets are properly set in production. Check that no debug or verbose logging is enabled.
89
- - **Performance Checks:** Ensure Redis and database are configured for expected load. Benchmark API endpoints and add indexes or cache hot queries as needed.
90
- - **Security Review:** Confirm TLS is enforced (no HTTP allowed). Test auth flows (invalid tokens are rejected, access is limited by role). Ensure network policies isolate components.