@stati/core 1.9.0 → 1.10.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.
Files changed (35) hide show
  1. package/README.md +6 -6
  2. package/dist/core/build.d.ts.map +1 -1
  3. package/dist/core/build.js +39 -4
  4. package/dist/core/dev.d.ts.map +1 -1
  5. package/dist/core/dev.js +70 -2
  6. package/dist/core/isg/hash.d.ts +14 -0
  7. package/dist/core/isg/hash.d.ts.map +1 -1
  8. package/dist/core/isg/hash.js +32 -1
  9. package/dist/core/isg/index.d.ts +1 -1
  10. package/dist/core/isg/index.d.ts.map +1 -1
  11. package/dist/core/isg/index.js +1 -1
  12. package/dist/core/isg/manifest.d.ts.map +1 -1
  13. package/dist/core/isg/manifest.js +7 -1
  14. package/dist/core/templates.d.ts.map +1 -1
  15. package/dist/core/templates.js +37 -6
  16. package/dist/core/utils/index.d.ts +2 -0
  17. package/dist/core/utils/index.d.ts.map +1 -1
  18. package/dist/core/utils/index.js +4 -0
  19. package/dist/core/utils/navigation-helpers.d.ts +124 -0
  20. package/dist/core/utils/navigation-helpers.d.ts.map +1 -0
  21. package/dist/core/utils/navigation-helpers.js +219 -0
  22. package/dist/core/utils/server.d.ts +1 -1
  23. package/dist/core/utils/server.d.ts.map +1 -1
  24. package/dist/core/utils/server.js +14 -1
  25. package/dist/core/utils/tailwind-inventory.d.ts +91 -0
  26. package/dist/core/utils/tailwind-inventory.d.ts.map +1 -0
  27. package/dist/core/utils/tailwind-inventory.js +228 -0
  28. package/dist/core/utils/template-utils.d.ts +3 -0
  29. package/dist/core/utils/template-utils.d.ts.map +1 -1
  30. package/dist/core/utils/template-utils.js +27 -3
  31. package/dist/types/content.d.ts +24 -3
  32. package/dist/types/content.d.ts.map +1 -1
  33. package/dist/types/isg.d.ts +4 -1
  34. package/dist/types/isg.d.ts.map +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Navigation helper utilities for template context.
3
+ * Provides methods for querying and traversing the navigation tree.
4
+ */
5
+ /**
6
+ * Finds a navigation node by its path or URL.
7
+ *
8
+ * @param tree - Navigation tree to search
9
+ * @param path - Path or URL to find
10
+ * @returns The found node or undefined
11
+ */
12
+ export function findNode(tree, path) {
13
+ for (const node of tree) {
14
+ // Check current node
15
+ if (node.path === path || node.url === path) {
16
+ return node;
17
+ }
18
+ // Recursively search children
19
+ if (node.children && node.children.length > 0) {
20
+ const found = findNode(node.children, path);
21
+ if (found) {
22
+ return found;
23
+ }
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ /**
29
+ * Gets the children of a specific navigation node by path.
30
+ *
31
+ * @param tree - Navigation tree to search
32
+ * @param path - Path of the parent node
33
+ * @returns Array of child nodes or empty array if not found or no children
34
+ */
35
+ export function getChildren(tree, path) {
36
+ const node = findNode(tree, path);
37
+ return node?.children || [];
38
+ }
39
+ /**
40
+ * Gets the parent node of a specific path.
41
+ *
42
+ * @param tree - Navigation tree to search
43
+ * @param path - Path to find parent for
44
+ * @returns The parent node or undefined
45
+ */
46
+ export function getParent(tree, path) {
47
+ return findParentNode(tree, path, null);
48
+ }
49
+ /**
50
+ * Helper function to find parent node recursively.
51
+ *
52
+ * @param nodes - Current nodes to search
53
+ * @param targetPath - Path we're looking for
54
+ * @param parent - Current parent node
55
+ * @returns The parent node or undefined
56
+ */
57
+ function findParentNode(nodes, targetPath, parent) {
58
+ for (const node of nodes) {
59
+ // Check if this node is the target
60
+ if (node.path === targetPath || node.url === targetPath) {
61
+ return parent || undefined;
62
+ }
63
+ // Recursively search children with this node as parent
64
+ if (node.children && node.children.length > 0) {
65
+ const found = findParentNode(node.children, targetPath, node);
66
+ if (found !== undefined) {
67
+ return found;
68
+ }
69
+ }
70
+ }
71
+ return undefined;
72
+ }
73
+ /**
74
+ * Gets the siblings of a specific path (nodes at the same level).
75
+ *
76
+ * @param tree - Navigation tree to search
77
+ * @param path - Path to find siblings for
78
+ * @param includeSelf - Whether to include the node itself in the results
79
+ * @returns Array of sibling nodes or empty array
80
+ */
81
+ export function getSiblings(tree, path, includeSelf = false) {
82
+ const parent = getParent(tree, path);
83
+ // If no parent, check if the node is at root level
84
+ if (!parent) {
85
+ // Node might be at root level
86
+ const isAtRoot = tree.some((node) => node.path === path || node.url === path);
87
+ if (isAtRoot) {
88
+ return includeSelf ? tree : tree.filter((node) => node.path !== path && node.url !== path);
89
+ }
90
+ return [];
91
+ }
92
+ const siblings = parent.children || [];
93
+ return includeSelf
94
+ ? siblings
95
+ : siblings.filter((node) => node.path !== path && node.url !== path);
96
+ }
97
+ /**
98
+ * Gets a subtree starting from a specific path.
99
+ *
100
+ * @param tree - Navigation tree to search
101
+ * @param path - Root path for the subtree
102
+ * @returns The subtree as an array (single node with its children) or empty array
103
+ */
104
+ export function getSubtree(tree, path) {
105
+ const node = findNode(tree, path);
106
+ return node ? [node] : [];
107
+ }
108
+ /**
109
+ * Gets the breadcrumb trail from root to a specific path.
110
+ *
111
+ * @param tree - Navigation tree to search
112
+ * @param path - Path to get breadcrumbs for
113
+ * @returns Array of nodes from root to the target path
114
+ */
115
+ export function getBreadcrumbs(tree, path) {
116
+ const trail = [];
117
+ findBreadcrumbTrail(tree, path, trail);
118
+ return trail;
119
+ }
120
+ /**
121
+ * Helper function to build breadcrumb trail recursively.
122
+ *
123
+ * @param nodes - Current nodes to search
124
+ * @param targetPath - Path we're looking for
125
+ * @param trail - Current breadcrumb trail
126
+ * @returns True if the target was found in this branch
127
+ */
128
+ function findBreadcrumbTrail(nodes, targetPath, trail) {
129
+ for (const node of nodes) {
130
+ // Add current node to trail
131
+ trail.push(node);
132
+ // Check if this is the target
133
+ if (node.path === targetPath || node.url === targetPath) {
134
+ return true;
135
+ }
136
+ // Check children
137
+ if (node.children && node.children.length > 0) {
138
+ if (findBreadcrumbTrail(node.children, targetPath, trail)) {
139
+ return true;
140
+ }
141
+ }
142
+ // This branch didn't contain the target, remove this node from trail
143
+ trail.pop();
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Finds the current page's navigation node.
149
+ *
150
+ * @param tree - Navigation tree to search
151
+ * @param currentPage - Current page model
152
+ * @returns The navigation node for the current page or undefined
153
+ */
154
+ export function getCurrentNode(tree, currentPage) {
155
+ return findNode(tree, currentPage.url);
156
+ }
157
+ /**
158
+ * Creates a navigation helpers object for template context.
159
+ *
160
+ * @param tree - The full navigation tree
161
+ * @param currentPage - The current page being rendered
162
+ * @returns Object with navigation helper methods
163
+ */
164
+ export function createNavigationHelpers(tree, currentPage) {
165
+ return {
166
+ /**
167
+ * The full navigation tree.
168
+ * Use stati.nav.tree to access the global navigation.
169
+ */
170
+ tree,
171
+ /**
172
+ * Gets the full navigation tree.
173
+ * @returns The complete navigation tree
174
+ */
175
+ getTree: () => tree,
176
+ /**
177
+ * Finds a navigation node by path or URL.
178
+ * @param path - The path or URL to find
179
+ * @returns The found node or undefined
180
+ */
181
+ findNode: (path) => findNode(tree, path),
182
+ /**
183
+ * Gets the children of a navigation node.
184
+ * @param path - The path of the parent node
185
+ * @returns Array of child navigation nodes
186
+ */
187
+ getChildren: (path) => getChildren(tree, path),
188
+ /**
189
+ * Gets the parent of a navigation node.
190
+ * @param path - The path to find the parent for (defaults to current page)
191
+ * @returns The parent node or undefined
192
+ */
193
+ getParent: (path) => getParent(tree, path || currentPage.url),
194
+ /**
195
+ * Gets the siblings of a navigation node.
196
+ * @param path - The path to find siblings for (defaults to current page)
197
+ * @param includeSelf - Whether to include the node itself
198
+ * @returns Array of sibling nodes
199
+ */
200
+ getSiblings: (path, includeSelf = false) => getSiblings(tree, path || currentPage.url, includeSelf),
201
+ /**
202
+ * Gets a subtree starting from a specific path.
203
+ * @param path - The root path for the subtree
204
+ * @returns Array containing the subtree
205
+ */
206
+ getSubtree: (path) => getSubtree(tree, path),
207
+ /**
208
+ * Gets the breadcrumb trail for a path.
209
+ * @param path - The path to get breadcrumbs for (defaults to current page)
210
+ * @returns Array of nodes from root to the target
211
+ */
212
+ getBreadcrumbs: (path) => getBreadcrumbs(tree, path || currentPage.url),
213
+ /**
214
+ * Gets the current page's navigation node.
215
+ * @returns The navigation node for the current page or undefined
216
+ */
217
+ getCurrentNode: () => getCurrentNode(tree, currentPage),
218
+ };
219
+ }
@@ -12,7 +12,7 @@ export interface PrettyUrlResult {
12
12
  * This handles common patterns like:
13
13
  * - /path/ -> /path/index.html
14
14
  * - /path/ -> /path.html (if no index.html exists)
15
- * - /path -> /path.html (when original path is not found)
15
+ * - /path -> /path.html (when original path is not found and has no extension)
16
16
  *
17
17
  * @param outDir The output directory to serve files from
18
18
  * @param requestPath The requested URL path
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CA+C1B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CA2D1B"}
@@ -5,7 +5,7 @@ import { stat } from 'fs/promises';
5
5
  * This handles common patterns like:
6
6
  * - /path/ -> /path/index.html
7
7
  * - /path/ -> /path.html (if no index.html exists)
8
- * - /path -> /path.html (when original path is not found)
8
+ * - /path -> /path.html (when original path is not found and has no extension)
9
9
  *
10
10
  * @param outDir The output directory to serve files from
11
11
  * @param requestPath The requested URL path
@@ -55,6 +55,19 @@ export async function resolvePrettyUrl(outDir, requestPath, originalFilePath) {
55
55
  // Continue to not found
56
56
  }
57
57
  }
58
+ else if (!requestPath.includes('.')) {
59
+ // For requests without trailing slash and without extension, try .html
60
+ const htmlPath = join(outDir, `${requestPath}.html`);
61
+ try {
62
+ const stats = await stat(htmlPath);
63
+ if (stats.isFile()) {
64
+ return { filePath: htmlPath, found: true };
65
+ }
66
+ }
67
+ catch {
68
+ // Continue to not found
69
+ }
70
+ }
58
71
  // No fallback worked
59
72
  return { filePath: null, found: false };
60
73
  }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Tailwind Class Inventory Management
3
+ *
4
+ * Tracks Tailwind classes used in templates (especially dynamic ones) and generates
5
+ * an HTML file that Tailwind can scan during its build process. This ensures that
6
+ * dynamically constructed classes (e.g., `from-${color}-50`) are included in the
7
+ * final CSS output.
8
+ */
9
+ /**
10
+ * Detects if Tailwind CSS is being used in the project by checking for:
11
+ * 1. tailwind.config.js or tailwind.config.ts
12
+ * 2. tailwindcss in package.json dependencies
13
+ *
14
+ * Results are cached to avoid repeated file system checks.
15
+ *
16
+ * @param projectRoot - Root directory of the project (defaults to current working directory)
17
+ * @returns True if Tailwind is detected, false otherwise
18
+ */
19
+ export declare function isTailwindUsed(projectRoot?: string): Promise<boolean>;
20
+ /**
21
+ * Resets the Tailwind detection cache.
22
+ * Useful for testing or when project dependencies change.
23
+ */
24
+ export declare function resetTailwindDetection(): void;
25
+ /**
26
+ * Loads classes from a previous inventory file and seeds the current inventory.
27
+ * This is useful to preserve dynamic classes from previous builds, especially
28
+ * during dev server startup where Tailwind may scan before templates are rendered.
29
+ *
30
+ * @param cacheDir - Directory where the inventory file is located (typically .stati/)
31
+ * @returns Number of classes loaded from the previous inventory
32
+ */
33
+ export declare function loadPreviousInventory(cacheDir: string): Promise<number>;
34
+ /**
35
+ * Tracks a Tailwind class in the inventory.
36
+ * Only tracks if inventory tracking is enabled.
37
+ *
38
+ * @param className - The class name to track
39
+ */
40
+ export declare function trackTailwindClass(className: string): void;
41
+ /**
42
+ * Enables inventory tracking for the current build/dev session.
43
+ * Should be called at the start of build or dev server.
44
+ */
45
+ export declare function enableInventoryTracking(): void;
46
+ /**
47
+ * Disables inventory tracking.
48
+ * Should be called after build completes or when stopping dev server.
49
+ */
50
+ export declare function disableInventoryTracking(): void;
51
+ /**
52
+ * Clears the inventory.
53
+ * Should be called at the start of each full build to ensure fresh tracking.
54
+ */
55
+ export declare function clearInventory(): void;
56
+ /**
57
+ * Gets the current inventory as an array.
58
+ * Useful for testing and debugging.
59
+ *
60
+ * @returns Array of tracked class names
61
+ */
62
+ export declare function getInventory(): string[];
63
+ /**
64
+ * Gets the count of tracked classes.
65
+ *
66
+ * @returns Number of classes in the inventory
67
+ */
68
+ export declare function getInventorySize(): number;
69
+ /**
70
+ * Checks if inventory tracking is currently enabled.
71
+ *
72
+ * @returns True if tracking is enabled
73
+ */
74
+ export declare function isTrackingEnabled(): boolean;
75
+ /**
76
+ * Writes the Tailwind class inventory to an HTML file that Tailwind can scan.
77
+ *
78
+ * The generated file contains all tracked classes in a hidden div.
79
+ * This file should be added to Tailwind's content configuration.
80
+ *
81
+ * @param cacheDir - Directory where the inventory file should be written (typically .stati/)
82
+ * @returns Path to the generated inventory file
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const inventoryPath = await writeTailwindClassInventory('/path/to/project/.stati');
87
+ * // File written to: /path/to/project/.stati/tailwind-classes.html
88
+ * ```
89
+ */
90
+ export declare function writeTailwindClassInventory(cacheDir: string): Promise<string>;
91
+ //# sourceMappingURL=tailwind-inventory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind-inventory.d.ts","sourceRoot":"","sources":["../../../src/core/utils/tailwind-inventory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsBH;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,WAAW,GAAE,MAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CA4C1F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC7E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAI1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6CnF"}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Tailwind Class Inventory Management
3
+ *
4
+ * Tracks Tailwind classes used in templates (especially dynamic ones) and generates
5
+ * an HTML file that Tailwind can scan during its build process. This ensures that
6
+ * dynamically constructed classes (e.g., `from-${color}-50`) are included in the
7
+ * final CSS output.
8
+ */
9
+ import { writeFile, ensureDir, pathExists, readFile } from './fs.js';
10
+ import { join } from 'path';
11
+ /**
12
+ * Module-level Set to track Tailwind classes across template renders.
13
+ * Cleared at the start of each full build.
14
+ */
15
+ const tailwindClassInventory = new Set();
16
+ /**
17
+ * Flag to track if inventory tracking is enabled.
18
+ * Only enabled during build/dev, not during production rendering.
19
+ */
20
+ let inventoryTrackingEnabled = false;
21
+ /**
22
+ * Cached result of Tailwind detection to avoid repeated file system checks.
23
+ */
24
+ let tailwindDetected = null;
25
+ /**
26
+ * Detects if Tailwind CSS is being used in the project by checking for:
27
+ * 1. tailwind.config.js or tailwind.config.ts
28
+ * 2. tailwindcss in package.json dependencies
29
+ *
30
+ * Results are cached to avoid repeated file system checks.
31
+ *
32
+ * @param projectRoot - Root directory of the project (defaults to current working directory)
33
+ * @returns True if Tailwind is detected, false otherwise
34
+ */
35
+ export async function isTailwindUsed(projectRoot = process.cwd()) {
36
+ // Return cached result if available
37
+ if (tailwindDetected !== null) {
38
+ return tailwindDetected;
39
+ }
40
+ // Check for tailwind.config.js or tailwind.config.ts
41
+ const configExists = (await pathExists(join(projectRoot, 'tailwind.config.js'))) ||
42
+ (await pathExists(join(projectRoot, 'tailwind.config.ts'))) ||
43
+ (await pathExists(join(projectRoot, 'tailwind.config.mjs'))) ||
44
+ (await pathExists(join(projectRoot, 'tailwind.config.cjs')));
45
+ if (configExists) {
46
+ tailwindDetected = true;
47
+ return true;
48
+ }
49
+ // Check package.json for tailwindcss dependency
50
+ try {
51
+ const packageJsonPath = join(projectRoot, 'package.json');
52
+ if (await pathExists(packageJsonPath)) {
53
+ const content = await readFile(packageJsonPath, 'utf-8');
54
+ if (!content) {
55
+ return false;
56
+ }
57
+ const packageJson = JSON.parse(content);
58
+ const deps = {
59
+ ...packageJson.dependencies,
60
+ ...packageJson.devDependencies,
61
+ };
62
+ if (deps.tailwindcss) {
63
+ tailwindDetected = true;
64
+ return true;
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // Ignore errors reading package.json
70
+ }
71
+ tailwindDetected = false;
72
+ return false;
73
+ }
74
+ /**
75
+ * Resets the Tailwind detection cache.
76
+ * Useful for testing or when project dependencies change.
77
+ */
78
+ export function resetTailwindDetection() {
79
+ tailwindDetected = null;
80
+ }
81
+ /**
82
+ * Loads classes from a previous inventory file and seeds the current inventory.
83
+ * This is useful to preserve dynamic classes from previous builds, especially
84
+ * during dev server startup where Tailwind may scan before templates are rendered.
85
+ *
86
+ * @param cacheDir - Directory where the inventory file is located (typically .stati/)
87
+ * @returns Number of classes loaded from the previous inventory
88
+ */
89
+ export async function loadPreviousInventory(cacheDir) {
90
+ const inventoryPath = join(cacheDir, 'tailwind-classes.html');
91
+ if (!(await pathExists(inventoryPath))) {
92
+ return 0;
93
+ }
94
+ try {
95
+ const content = await readFile(inventoryPath, 'utf-8');
96
+ if (!content) {
97
+ return 0;
98
+ }
99
+ // Extract classes from the div's class attribute
100
+ // Looking for: <div class="class1 class2 class3"></div>
101
+ const classMatch = content.match(/<div class="([^"]+)"><\/div>/);
102
+ if (!classMatch || !classMatch[1]) {
103
+ return 0;
104
+ }
105
+ const classes = classMatch[1].split(/\s+/).filter(Boolean);
106
+ // Track each class from the previous inventory
107
+ for (const className of classes) {
108
+ trackTailwindClass(className);
109
+ }
110
+ return classes.length;
111
+ }
112
+ catch {
113
+ // If we can't read or parse the file, just return 0
114
+ return 0;
115
+ }
116
+ }
117
+ /**
118
+ * Tracks a Tailwind class in the inventory.
119
+ * Only tracks if inventory tracking is enabled.
120
+ *
121
+ * @param className - The class name to track
122
+ */
123
+ export function trackTailwindClass(className) {
124
+ if (inventoryTrackingEnabled && className) {
125
+ tailwindClassInventory.add(className);
126
+ }
127
+ }
128
+ /**
129
+ * Enables inventory tracking for the current build/dev session.
130
+ * Should be called at the start of build or dev server.
131
+ */
132
+ export function enableInventoryTracking() {
133
+ inventoryTrackingEnabled = true;
134
+ }
135
+ /**
136
+ * Disables inventory tracking.
137
+ * Should be called after build completes or when stopping dev server.
138
+ */
139
+ export function disableInventoryTracking() {
140
+ inventoryTrackingEnabled = false;
141
+ }
142
+ /**
143
+ * Clears the inventory.
144
+ * Should be called at the start of each full build to ensure fresh tracking.
145
+ */
146
+ export function clearInventory() {
147
+ tailwindClassInventory.clear();
148
+ }
149
+ /**
150
+ * Gets the current inventory as an array.
151
+ * Useful for testing and debugging.
152
+ *
153
+ * @returns Array of tracked class names
154
+ */
155
+ export function getInventory() {
156
+ return Array.from(tailwindClassInventory).sort();
157
+ }
158
+ /**
159
+ * Gets the count of tracked classes.
160
+ *
161
+ * @returns Number of classes in the inventory
162
+ */
163
+ export function getInventorySize() {
164
+ return tailwindClassInventory.size;
165
+ }
166
+ /**
167
+ * Checks if inventory tracking is currently enabled.
168
+ *
169
+ * @returns True if tracking is enabled
170
+ */
171
+ export function isTrackingEnabled() {
172
+ return inventoryTrackingEnabled;
173
+ }
174
+ /**
175
+ * Writes the Tailwind class inventory to an HTML file that Tailwind can scan.
176
+ *
177
+ * The generated file contains all tracked classes in a hidden div.
178
+ * This file should be added to Tailwind's content configuration.
179
+ *
180
+ * @param cacheDir - Directory where the inventory file should be written (typically .stati/)
181
+ * @returns Path to the generated inventory file
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const inventoryPath = await writeTailwindClassInventory('/path/to/project/.stati');
186
+ * // File written to: /path/to/project/.stati/tailwind-classes.html
187
+ * ```
188
+ */
189
+ export async function writeTailwindClassInventory(cacheDir) {
190
+ await ensureDir(cacheDir);
191
+ const inventoryPath = join(cacheDir, 'tailwind-classes.html');
192
+ const classes = getInventory();
193
+ // Generate HTML with all tracked classes
194
+ // Using hidden div so it's scanned by Tailwind but not rendered
195
+ const html = `<!--
196
+ Auto-generated by Stati - DO NOT EDIT
197
+
198
+ This file contains dynamically-generated Tailwind classes extracted from your templates.
199
+ It ensures that Tailwind's JIT compiler generates CSS for classes built with template
200
+ variables (e.g., from-\${color}-50).
201
+
202
+ Add this file to your tailwind.config.js content array:
203
+ content: [
204
+ './site/**/*.{md,eta,html}',
205
+ './.stati/tailwind-classes.html'
206
+ ]
207
+
208
+ Generated: ${new Date().toISOString()}
209
+ Classes tracked: ${classes.length}
210
+ -->
211
+ <!DOCTYPE html>
212
+ <html>
213
+ <head>
214
+ <meta charset="UTF-8">
215
+ <title>Stati Tailwind Class Inventory</title>
216
+ </head>
217
+ <body>
218
+ <div class="hidden">
219
+ ${classes.length > 0
220
+ ? `<div class="${classes.join(' ')}"></div>`
221
+ : '<!-- No dynamic classes tracked yet -->'}
222
+ </div>
223
+ </body>
224
+ </html>
225
+ `;
226
+ await writeFile(inventoryPath, html, 'utf-8');
227
+ return inventoryPath;
228
+ }
@@ -6,6 +6,9 @@ type PropValueArg = string | number | boolean | null | undefined | Record<string
6
6
  * Builds a property value from various inputs, similar to classnames but for any property.
7
7
  * Accepts strings, arrays, and objects. Filters out falsy values.
8
8
  *
9
+ * Also tracks Tailwind classes for the class inventory when inventory tracking is enabled.
10
+ * This ensures dynamically-generated Tailwind classes are included in the CSS build.
11
+ *
9
12
  * @param args - Values to combine
10
13
  * @returns Combined property value string
11
14
  *
@@ -1 +1 @@
1
- {"version":3,"file":"template-utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,YAAY,GACb,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAC5D,YAAY,EAAE,CAAC;AAEnB;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,CAwBzD"}
1
+ {"version":3,"file":"template-utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,KAAK,YAAY,GACb,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAC5D,YAAY,EAAE,CAAC;AAEnB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,CA8CzD"}
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Utility functions for Eta templates
3
3
  */
4
+ import { trackTailwindClass } from './tailwind-inventory.js';
4
5
  /**
5
6
  * Builds a property value from various inputs, similar to classnames but for any property.
6
7
  * Accepts strings, arrays, and objects. Filters out falsy values.
7
8
  *
9
+ * Also tracks Tailwind classes for the class inventory when inventory tracking is enabled.
10
+ * This ensures dynamically-generated Tailwind classes are included in the CSS build.
11
+ *
8
12
  * @param args - Values to combine
9
13
  * @returns Combined property value string
10
14
  *
@@ -20,17 +24,37 @@ export function propValue(...args) {
20
24
  if (!arg)
21
25
  continue;
22
26
  if (typeof arg === 'string' || typeof arg === 'number') {
23
- classes.push(String(arg));
27
+ const classStr = String(arg);
28
+ classes.push(classStr);
29
+ // Track ALL classes for Tailwind inventory
30
+ // Split space-separated classes and track each one individually
31
+ const individualClasses = classStr.split(/\s+/).filter(Boolean);
32
+ for (const cls of individualClasses) {
33
+ trackTailwindClass(cls);
34
+ }
24
35
  }
25
36
  else if (Array.isArray(arg)) {
26
- classes.push(...arg
37
+ const arrayClasses = arg
27
38
  .filter((item) => item && (typeof item === 'string' || typeof item === 'number'))
28
- .map(String));
39
+ .map(String);
40
+ classes.push(...arrayClasses);
41
+ // Track each class for Tailwind inventory
42
+ for (const classStr of arrayClasses) {
43
+ const individualClasses = classStr.split(/\s+/).filter(Boolean);
44
+ for (const cls of individualClasses) {
45
+ trackTailwindClass(cls);
46
+ }
47
+ }
29
48
  }
30
49
  else if (typeof arg === 'object') {
31
50
  for (const [key, value] of Object.entries(arg)) {
32
51
  if (value) {
33
52
  classes.push(key);
53
+ // Track for Tailwind inventory
54
+ const individualClasses = key.split(/\s+/).filter(Boolean);
55
+ for (const cls of individualClasses) {
56
+ trackTailwindClass(cls);
57
+ }
34
58
  }
35
59
  }
36
60
  }