@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.
- package/README.md +6 -6
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +39 -4
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +70 -2
- package/dist/core/isg/hash.d.ts +14 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +32 -1
- package/dist/core/isg/index.d.ts +1 -1
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +1 -1
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +7 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +37 -6
- package/dist/core/utils/index.d.ts +2 -0
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +4 -0
- package/dist/core/utils/navigation-helpers.d.ts +124 -0
- package/dist/core/utils/navigation-helpers.d.ts.map +1 -0
- package/dist/core/utils/navigation-helpers.js +219 -0
- package/dist/core/utils/server.d.ts +1 -1
- package/dist/core/utils/server.d.ts.map +1 -1
- package/dist/core/utils/server.js +14 -1
- package/dist/core/utils/tailwind-inventory.d.ts +91 -0
- package/dist/core/utils/tailwind-inventory.d.ts.map +1 -0
- package/dist/core/utils/tailwind-inventory.js +228 -0
- package/dist/core/utils/template-utils.d.ts +3 -0
- package/dist/core/utils/template-utils.d.ts.map +1 -1
- package/dist/core/utils/template-utils.js +27 -3
- package/dist/types/content.d.ts +24 -3
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/isg.d.ts +4 -1
- package/dist/types/isg.d.ts.map +1 -1
- 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,
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|