@pixelated-tech/components 3.7.0 → 3.7.3

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.
@@ -0,0 +1,34 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ const publicDir = path.join(process.cwd(), 'public');
9
+ const outFile = path.join(publicDir, 'site-images.json');
10
+
11
+ function listFiles(dir, exts = ['.jpg','.jpeg','.png','.webp','.avif','.gif']) {
12
+ const out = [];
13
+ if (!fs.existsSync(dir)) return out;
14
+ const items = fs.readdirSync(dir, { withFileTypes: true });
15
+ for (const it of items) {
16
+ const full = path.join(dir, it.name);
17
+ if (it.isDirectory()) out.push(...listFiles(full, exts));
18
+ else if (exts.includes(path.extname(it.name).toLowerCase())) {
19
+ out.push(path.relative(publicDir, full).replace(/\\\\/g, '/'));
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+
25
+ const files = listFiles(publicDir);
26
+ const manifest = {
27
+ metadata: {
28
+ generatedAt: new Date().toISOString(),
29
+ imageCount: files.length,
30
+ },
31
+ images: files,
32
+ };
33
+ fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2), 'utf8');
34
+ console.log('Wrote', outFile, 'with', files.length, 'images and metadata');
@@ -0,0 +1,205 @@
1
+ import { isClientComponent } from '../components/utilities/functions.ts';
2
+
3
+ export default {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Enforce PropTypes + InferProps pattern for React components',
8
+ category: 'Best Practices',
9
+ recommended: true,
10
+ },
11
+ fixable: false,
12
+ schema: [],
13
+ messages: {
14
+ missingPropTypes: 'Component "{{componentName}}" is missing propTypes. Add: {{componentName}}.propTypes = { ... }; immediately above the function.',
15
+ missingInferProps: 'Component "{{componentName}}" is missing InferProps type. Add: export type {{componentName}}Type = InferProps<typeof {{componentName}}.propTypes>; immediately above the function.',
16
+ invalidInferProps: 'InferProps type for "{{componentName}}" must be named "{{componentName}}Type" and exported. Rename and add export.',
17
+ missingInferPropsUsage: 'Component "{{componentName}}" function parameters must use the InferProps type. Change: export function {{componentName}}(props: {{componentName}}Type)',
18
+ propTypesPlacement: 'Component "{{componentName}}" propTypes must be defined immediately above the function declaration with no blank lines. Move {{componentName}}.propTypes = { ... }; right above the function.',
19
+ inferPropsPlacement: 'Component "{{componentName}}" InferProps type must be defined immediately above the function declaration with no blank lines. Move export type {{componentName}}Type = ...; right above the function.',
20
+ },
21
+ },
22
+ create(context) {
23
+
24
+ const components = new Map(); // Track components and their patterns
25
+
26
+ function checkForInferProps(typeAnnotation) {
27
+ if (!typeAnnotation) return false;
28
+
29
+ if (typeAnnotation.type === 'TSTypeReference' && typeAnnotation.typeName?.name === 'InferProps') {
30
+ return true;
31
+ }
32
+
33
+ if (typeAnnotation.type === 'TSIntersectionType') {
34
+ return typeAnnotation.types.some(checkForInferProps);
35
+ }
36
+
37
+ return false;
38
+ }
39
+
40
+ function extractComponentNameFromInferProps(node) {
41
+ // For our pattern of ComponentType = InferProps<typeof Component.propTypes>
42
+ // We can simply remove 'Type' from the type name
43
+ return node.id.name.replace('Type', '');
44
+ }
45
+
46
+ function reportViolations(component) {
47
+ const { functionNode, hasPropTypes, hasInferProps, usesInferProps, inferPropsName, propTypesNode, inferPropsNode } = component;
48
+ if (!functionNode) return; // Skip if function not found yet
49
+
50
+ const componentName = functionNode.id.name;
51
+
52
+ if (!hasPropTypes) {
53
+ context.report({
54
+ node: functionNode,
55
+ messageId: 'missingPropTypes',
56
+ data: { componentName },
57
+ });
58
+ }
59
+
60
+ if (!hasInferProps) {
61
+ context.report({
62
+ node: functionNode,
63
+ messageId: 'missingInferProps',
64
+ data: { componentName },
65
+ });
66
+ }
67
+
68
+ if (hasPropTypes && hasInferProps && !usesInferProps && functionNode.params.length > 0) {
69
+ context.report({
70
+ node: functionNode,
71
+ messageId: 'missingInferPropsUsage',
72
+ data: { componentName, inferPropsName },
73
+ });
74
+ }
75
+
76
+ // Check placement and ordering: propTypes -> InferProps -> function (consecutive, no empty lines)
77
+ if (hasPropTypes && hasInferProps && propTypesNode && inferPropsNode) {
78
+ const propTypesEndLine = propTypesNode.loc.end.line;
79
+ const inferPropsLine = inferPropsNode.loc.start.line;
80
+ const functionLine = functionNode.loc.start.line;
81
+
82
+ // InferProps must immediately follow propTypes (no empty lines)
83
+ if (inferPropsLine !== propTypesEndLine + 1) {
84
+ context.report({
85
+ node: inferPropsNode,
86
+ messageId: 'inferPropsPlacement',
87
+ data: { componentName },
88
+ });
89
+ }
90
+
91
+ // Function must immediately follow InferProps (no empty lines)
92
+ if (functionLine !== inferPropsLine + 1) {
93
+ context.report({
94
+ node: functionNode,
95
+ messageId: 'propTypesPlacement',
96
+ data: { componentName },
97
+ });
98
+ }
99
+ }
100
+ }
101
+
102
+ return {
103
+ // Find component function declarations
104
+ FunctionDeclaration(node) {
105
+ if (node.id && node.id.name && node.parent.type === 'ExportNamedDeclaration') {
106
+ const componentName = node.id.name;
107
+
108
+ // Check if this is a client component (contains client-only patterns)
109
+ const sourceCode = context.getSourceCode();
110
+ const fileContent = sourceCode.text;
111
+ if (componentName[0] === componentName[0].toUpperCase() && isClientComponent(fileContent)) {
112
+ if (!components.has(componentName)) {
113
+ components.set(componentName, {
114
+ functionNode: node,
115
+ hasPropTypes: false,
116
+ hasInferProps: false,
117
+ inferPropsName: `${componentName}Type`,
118
+ usesInferProps: false,
119
+ propTypesNode: null,
120
+ inferPropsNode: null,
121
+ });
122
+ } else {
123
+ // Component entry already exists (e.g., from propTypes), just update functionNode
124
+ components.get(componentName).functionNode = node;
125
+ }
126
+ }
127
+ }
128
+ },
129
+
130
+ // Find PropTypes assignments
131
+ AssignmentExpression(node) {
132
+ if (
133
+ node.left.type === 'MemberExpression' &&
134
+ node.left.object.type === 'Identifier' &&
135
+ node.left.property.name === 'propTypes'
136
+ ) {
137
+ const componentName = node.left.object.name;
138
+ if (!components.has(componentName)) {
139
+ // Component might be declared later, create entry now
140
+ components.set(componentName, {
141
+ functionNode: null, // Will be set when function is found
142
+ hasPropTypes: false,
143
+ hasInferProps: false,
144
+ inferPropsName: `${componentName}Type`,
145
+ usesInferProps: false,
146
+ propTypesNode: null,
147
+ inferPropsNode: null,
148
+ });
149
+ }
150
+ const component = components.get(componentName);
151
+ component.hasPropTypes = true;
152
+ component.propTypesNode = node;
153
+ }
154
+ },
155
+
156
+ // Find InferProps type declarations
157
+ TSTypeAliasDeclaration(node) {
158
+ if (node.parent.type === 'ExportNamedDeclaration') {
159
+ const componentName = extractComponentNameFromInferProps(node);
160
+ if (componentName && components.has(componentName)) {
161
+ const component = components.get(componentName);
162
+ if (node.id.name === component.inferPropsName) {
163
+ // Check if type annotation contains InferProps
164
+ const hasInferProps = checkForInferProps(node.typeAnnotation);
165
+ if (hasInferProps) {
166
+ component.hasInferProps = true;
167
+ component.inferPropsNode = node;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ },
173
+
174
+ // Check function parameters
175
+ 'FunctionDeclaration:exit'(node) {
176
+ if (node.id && components.has(node.id.name)) {
177
+ const component = components.get(node.id.name);
178
+
179
+ // Check if function uses the InferProps type
180
+ if (node.params.length === 1) {
181
+ const param = node.params[0];
182
+
183
+ // Handle both direct type annotation and destructured parameters
184
+ let paramTypeName = null;
185
+
186
+ if (param.type === 'Identifier' && param.typeAnnotation?.typeAnnotation?.type === 'TSTypeReference') {
187
+ // Direct parameter: (props: ComponentType)
188
+ paramTypeName = param.typeAnnotation.typeAnnotation.typeName?.name;
189
+ } else if (param.type === 'ObjectPattern' && param.typeAnnotation?.typeAnnotation?.type === 'TSTypeReference') {
190
+ // Destructured parameter: ({ prop }: ComponentType)
191
+ paramTypeName = param.typeAnnotation.typeAnnotation.typeName?.name;
192
+ }
193
+
194
+ if (paramTypeName === component.inferPropsName) {
195
+ component.usesInferProps = true;
196
+ }
197
+ }
198
+
199
+ // Report violations
200
+ reportViolations(component);
201
+ }
202
+ },
203
+ };
204
+ },
205
+ };
@@ -0,0 +1,194 @@
1
+ #!/bin/bash
2
+
3
+ # Universal Release Script for Pixelated Projects
4
+ # This script builds in dev, updates main, and optionally publishes to npm
5
+
6
+ set -e # Exit on any error
7
+
8
+ # Get project name from package.json
9
+ PROJECT_NAME=$(node -p "require('./package.json').name" 2>/dev/null || echo "unknown-project")
10
+
11
+ # Function to prompt for remote selection
12
+ prompt_remote_selection() {
13
+ echo "Available git remotes:" >&2
14
+ local remotes=($(git remote))
15
+ local i=1
16
+ for remote in "${remotes[@]}"; do
17
+ echo "$i) $remote" >&2
18
+ ((i++))
19
+ done
20
+
21
+ local choice
22
+ read -p "Select remote to use (1-${#remotes[@]}): " choice >&2
23
+
24
+ case $choice in
25
+ [1-9]|[1-9][0-9])
26
+ if [ "$choice" -le "${#remotes[@]}" ]; then
27
+ echo "${remotes[$((choice-1))]}"
28
+ else
29
+ echo "${remotes[0]}" # Default to first if invalid
30
+ fi
31
+ ;;
32
+ *) echo "${remotes[0]}" ;; # Default to first remote
33
+ esac
34
+ }
35
+
36
+ # Select remote
37
+ REMOTE_NAME=$(prompt_remote_selection)
38
+
39
+ echo "🚀 Starting Release Process for $PROJECT_NAME"
40
+ echo "================================================="
41
+
42
+ # Function to get current version
43
+ get_current_version() {
44
+ node -p "require('./package.json').version"
45
+ }
46
+
47
+ # Function to prompt for version bump type
48
+ prompt_version_type() {
49
+ echo "Current version: $(get_current_version)" >&2
50
+ echo "Select version bump type:" >&2
51
+ echo "1) patch (x.x.1)" >&2
52
+ echo "2) minor (x.1.0)" >&2
53
+ echo "3) major (1.x.x)" >&2
54
+ echo "4) custom version" >&2
55
+ echo "5) no version bump" >&2
56
+ read -p "Enter choice (1-5): " choice >&2
57
+ case $choice in
58
+ 1) version_type="patch" ;;
59
+ 2) version_type="minor" ;;
60
+ 3) version_type="major" ;;
61
+ 4)
62
+ read -p "Enter custom version: " custom_version >&2
63
+ version_type="$custom_version"
64
+ ;;
65
+ 5) version_type="none" ;;
66
+ *) version_type="patch" ;; # default
67
+ esac
68
+ }
69
+
70
+ # Function to prompt for commit message
71
+ prompt_commit_message() {
72
+ read -p "Enter commit message (or press enter for default): " commit_msg
73
+ if [ -z "$commit_msg" ]; then
74
+ echo "chore: release $(get_current_version)"
75
+ else
76
+ echo "$commit_msg"
77
+ fi
78
+ }
79
+
80
+ # Function to prompt for publishing
81
+ prompt_publish() {
82
+ read -p "Do you want to publish to npm? (y/n): " should_publish
83
+ if [ "$should_publish" = "y" ] || [ "$should_publish" = "Y" ]; then
84
+ echo "yes"
85
+ else
86
+ echo "no"
87
+ fi
88
+ }
89
+
90
+ # Function to prompt for OTP
91
+ prompt_otp() {
92
+ read -p "Enter npm OTP: " otp
93
+ echo "$otp"
94
+ }
95
+
96
+ # Check if we're on dev branch
97
+ current_branch=$(git branch --show-current)
98
+ if [ "$current_branch" != "dev" ]; then
99
+ echo "❌ Error: Must be on dev branch to run this script"
100
+ echo "Current branch: $current_branch"
101
+ echo "Please switch to dev branch: git checkout dev"
102
+ exit 1
103
+ fi
104
+
105
+ echo "📦 Step 1: Updating dependencies..."
106
+ npm outdated | awk 'NR>1 {print $1"@"$4}' | xargs npm install --force --save 2>/dev/null || true
107
+ npm audit fix --force 2>/dev/null || true
108
+
109
+ echo "🔍 Step 2: Running lint..."
110
+ npm run lint
111
+
112
+ echo "🔨 Step 3: Building project..."
113
+ npm run build
114
+
115
+ echo "🏷️ Step 4: Version bump..."
116
+ prompt_version_type
117
+ if [ "$version_type" != "none" ]; then
118
+ if [ "$version_type" = "patch" ] || [ "$version_type" = "minor" ] || [ "$version_type" = "major" ]; then
119
+ npm version $version_type --force --no-git-tag-version
120
+ else
121
+ # Custom version
122
+ npm version $version_type --force --no-git-tag-version
123
+ fi
124
+ fi
125
+
126
+ echo "💾 Step 5: Committing changes..."
127
+ commit_message=$(prompt_commit_message)
128
+ git add . -v
129
+ if git diff --cached --quiet; then
130
+ echo "ℹ️ No changes to commit"
131
+ else
132
+ git commit -m "$commit_message"
133
+ fi
134
+
135
+ echo "📤 Step 6: Pushing dev branch..."
136
+ # Try to push, if it fails due to remote changes, fetch and rebase
137
+ if ! git push $REMOTE_NAME dev; then
138
+ echo "⚠️ Push failed, fetching remote changes and rebasing..."
139
+ git fetch $REMOTE_NAME
140
+ if git rebase $REMOTE_NAME/dev; then
141
+ echo "✅ Rebased successfully, pushing..."
142
+ git push $REMOTE_NAME dev || {
143
+ echo "❌ Failed to push after rebase. Please check git status."
144
+ exit 1
145
+ }
146
+ else
147
+ echo "❌ Rebase failed. Please resolve conflicts and run 'git rebase --continue' or 'git rebase --abort'"
148
+ exit 1
149
+ fi
150
+ fi
151
+
152
+ echo "🔄 Step 7: Updating main branch..."
153
+ # Force main to match dev exactly
154
+ git push $REMOTE_NAME dev:main --force
155
+
156
+ # Also update main locally if it exists
157
+ if git show-ref --verify --quiet refs/heads/main; then
158
+ git branch -D main 2>/dev/null || true
159
+ git checkout -b main
160
+ git reset --hard dev
161
+ git push $REMOTE_NAME main --force
162
+ git checkout dev
163
+ fi
164
+
165
+ echo "🏷️ Step 8: Creating and pushing git tag..."
166
+ new_version=$(get_current_version)
167
+ if ! git tag -l | grep -q "v$new_version"; then
168
+ git tag "v$new_version"
169
+ git push $REMOTE_NAME "v$new_version"
170
+ else
171
+ echo "ℹ️ Tag v$new_version already exists"
172
+ fi
173
+
174
+ echo "🔐 Step 9: Publishing to npm..."
175
+ should_publish=$(prompt_publish)
176
+ if [ "$should_publish" = "yes" ]; then
177
+ npm login
178
+ otp=$(prompt_otp)
179
+ npm publish --access public --otp=$otp
180
+ echo "✅ Successfully published to npm!"
181
+ else
182
+ echo "ℹ️ Skipping npm publish"
183
+ echo "You can publish manually with: npm publish --access public"
184
+ fi
185
+
186
+ echo ""
187
+ echo "✅ Release complete!"
188
+ echo "📦 Version: $(get_current_version)"
189
+ echo "📋 Branches updated: dev, main"
190
+ echo "🏷️ Tag created: v$(get_current_version)"
191
+
192
+ if [ "$should_publish" = "yes" ]; then
193
+ echo "🔗 https://www.npmjs.com/package/$PROJECT_NAME"
194
+ fi
@@ -0,0 +1,280 @@
1
+ import fs from 'fs';
2
+ import glob from 'glob';
3
+ import { CLIENT_ONLY_PATTERNS, TS_FILE_IGNORE_PATTERNS, TSX_FILE_IGNORE_PATTERNS, SERVER_ONLY_PATTERNS } from '../components/utilities/functions.js';
4
+
5
+ console.log('🔍 Validating exports...\n');
6
+
7
+ // Find all .ts files (excluding .d.ts, test files, stories, examples, types.ts)
8
+ const tsFiles = glob.sync('src/components/**/*.ts', {
9
+ ignore: TS_FILE_IGNORE_PATTERNS
10
+ });
11
+
12
+ // Find all .tsx files (excluding test files, stories, examples)
13
+ const tsxFiles = glob.sync('src/components/**/*.tsx', {
14
+ ignore: TSX_FILE_IGNORE_PATTERNS
15
+ });
16
+
17
+ // Combine all component files
18
+ const allComponentFiles = [...tsFiles, ...tsxFiles];
19
+
20
+ // Analyze each component file to determine if it's client-required or server-safe
21
+ function analyzeComponentFile(filePath) {
22
+ const content = fs.readFileSync(filePath, 'utf8');
23
+
24
+ // Server-only patterns that indicate this should only be on server (not client)
25
+ // (Imported from shared utilities)
26
+
27
+ // Client-only patterns that require the component to run on client
28
+ // (Imported from shared utilities)
29
+
30
+ // Check if file contains any server-only patterns
31
+ const isServerOnly = SERVER_ONLY_PATTERNS.some(pattern => pattern.test(content));
32
+
33
+ // Check if file contains any client-only patterns
34
+ const isClientOnly = CLIENT_ONLY_PATTERNS.some(pattern => pattern.test(content));
35
+
36
+ return {
37
+ filePath,
38
+ isClientOnly,
39
+ isServerOnly,
40
+ exportPath: filePath.replace('src/', './').replace(/\.tsx?$/, '')
41
+ };
42
+ }
43
+
44
+ // Analyze all component files
45
+ const analyzedComponents = allComponentFiles.map(analyzeComponentFile);
46
+
47
+ // Special handling for utilities/functions.ts - it's safe for both client and server despite containing pattern definitions
48
+ analyzedComponents.forEach(comp => {
49
+ if (comp.exportPath === './components/utilities/functions') {
50
+ comp.isClientOnly = false;
51
+ comp.isServerOnly = false;
52
+ }
53
+ });
54
+
55
+ // Separate admin and non-admin components
56
+ const adminComponents = analyzedComponents.filter(comp => comp.exportPath.startsWith('./components/admin/'));
57
+ const nonAdminComponents = analyzedComponents.filter(comp => !comp.exportPath.startsWith('./components/admin/'));
58
+
59
+ // Create arrays of components for each bundle (non-admin only)
60
+ // If a component has server-only patterns, it's server-only; if client-only and not server-only, client-only; else safe
61
+ const serverOnlyComponents = nonAdminComponents.filter(comp => comp.isServerOnly);
62
+ const clientOnlyComponents = nonAdminComponents.filter(comp => comp.isClientOnly && !comp.isServerOnly);
63
+ const clientAndServerSafeComponents = nonAdminComponents.filter(comp => !comp.isClientOnly && !comp.isServerOnly);
64
+
65
+ // Read index files
66
+ const indexServer = fs.readFileSync('src/index.server.js', 'utf8');
67
+ const indexClient = fs.readFileSync('src/index.js', 'utf8');
68
+ const indexAdminServer = fs.readFileSync('src/index.adminserver.js', 'utf8');
69
+ const indexAdminClient = fs.readFileSync('src/index.adminclient.js', 'utf8');
70
+
71
+ // Helper function to extract exports from index file
72
+ function extractExports(content) {
73
+ // Remove comments
74
+ content = content.replace(/\/\*[\s\S]*?\*\//g, '');
75
+ content = content.replace(/\/\/.*$/gm, '');
76
+
77
+ const exports = [];
78
+
79
+ // Handle export * from './path' syntax
80
+ const exportAllRegex = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g;
81
+ let match;
82
+
83
+ while ((match = exportAllRegex.exec(content)) !== null) {
84
+ exports.push(match[1]);
85
+ }
86
+
87
+ // Handle export { ... } from './path' syntax
88
+ const exportRegex = /export\s+{\s*([^}]+)\s*}\s+from\s+['"]([^'"]+)['"]/g;
89
+ while ((match = exportRegex.exec(content)) !== null) {
90
+ const exportList = match[1];
91
+ const exportItems = exportList.split(',').map(item => item.trim());
92
+ exports.push(...exportItems.map(item => match[2])); // Use the from path
93
+ }
94
+
95
+ return exports;
96
+ }
97
+
98
+ // Extract exports from all index files
99
+ const serverExports = extractExports(indexServer);
100
+ const clientExports = extractExports(indexClient);
101
+ const adminServerExports = extractExports(indexAdminServer);
102
+ const adminClientExports = extractExports(indexAdminClient);
103
+
104
+ const missing = {
105
+ server: [],
106
+ client: [],
107
+ adminServer: [],
108
+ adminClient: [],
109
+ files: [] // New: exported paths that don't correspond to existing files
110
+ };
111
+
112
+ const bundleErrors = {
113
+ server: [], // Client-only components incorrectly in server bundle
114
+ client: [], // Server-only components incorrectly in client bundle
115
+ adminServer: [], // Client-only components incorrectly in admin server bundle
116
+ adminClient: [] // Server-only components incorrectly in admin client bundle
117
+ };
118
+
119
+ // Check if exported paths correspond to existing files
120
+ function checkExportPathsExist(exports, bundleName) {
121
+ exports.forEach(exportPath => {
122
+ // Convert export path to file path (./components/... -> src/components/...)
123
+ const filePathBase = exportPath.replace('./', 'src/');
124
+ const tsFile = `${filePathBase}.ts`;
125
+ const tsxFile = `${filePathBase}.tsx`;
126
+
127
+ const tsExists = fs.existsSync(tsFile);
128
+ const tsxExists = fs.existsSync(tsxFile);
129
+
130
+ if (!tsExists && !tsxExists) {
131
+ missing.files.push(`${bundleName}: ${exportPath} (.ts/.tsx files not found)`);
132
+ }
133
+ });
134
+ }
135
+
136
+ checkExportPathsExist(serverExports, 'server bundle');
137
+ checkExportPathsExist(clientExports, 'client bundle');
138
+ checkExportPathsExist(adminServerExports, 'admin server bundle');
139
+ checkExportPathsExist(adminClientExports, 'admin client bundle');
140
+
141
+ // Check for missing exports
142
+ clientAndServerSafeComponents.forEach(comp => {
143
+ if (!serverExports.includes(comp.exportPath)) {
144
+ missing.server.push(comp.exportPath);
145
+ }
146
+ });
147
+
148
+ serverOnlyComponents.forEach(comp => {
149
+ if (!serverExports.includes(comp.exportPath)) {
150
+ missing.server.push(comp.exportPath);
151
+ }
152
+ });
153
+
154
+ clientOnlyComponents.forEach(comp => {
155
+ if (!clientExports.includes(comp.exportPath)) {
156
+ missing.client.push(comp.exportPath);
157
+ }
158
+ });
159
+
160
+ clientAndServerSafeComponents.forEach(comp => {
161
+ if (!clientExports.includes(comp.exportPath)) {
162
+ missing.client.push(comp.exportPath);
163
+ }
164
+ });
165
+
166
+ // Check admin components - separate server and client bundles
167
+ const serverRelevantAdmin = adminComponents.filter(comp => comp.isServerOnly || !comp.isClientOnly);
168
+ const clientRelevantAdmin = adminComponents.filter(comp => comp.isClientOnly || !comp.isServerOnly);
169
+
170
+ serverRelevantAdmin.forEach(comp => {
171
+ if (!adminServerExports.includes(comp.exportPath)) {
172
+ missing.adminServer.push(comp.exportPath);
173
+ }
174
+ if (serverExports.includes(comp.exportPath) && !comp.isServerOnly) {
175
+ bundleErrors.server.push(comp.exportPath + ' (admin component in server bundle)');
176
+ }
177
+ if (clientExports.includes(comp.exportPath) && !comp.isServerOnly) {
178
+ bundleErrors.client.push(comp.exportPath + ' (admin component in client bundle)');
179
+ }
180
+ });
181
+
182
+ clientRelevantAdmin.forEach(comp => {
183
+ if (!adminClientExports.includes(comp.exportPath)) {
184
+ missing.adminClient.push(comp.exportPath);
185
+ }
186
+ if (serverExports.includes(comp.exportPath) && comp.isClientOnly) {
187
+ bundleErrors.server.push(comp.exportPath + ' (client-only admin component in server bundle)');
188
+ }
189
+ // Allow client-only components in adminserver if they are also server-only (both)
190
+ if (adminServerExports.includes(comp.exportPath) && comp.isClientOnly && !comp.isServerOnly) {
191
+ bundleErrors.adminServer.push(comp.exportPath + ' (client-only admin component in admin server bundle)');
192
+ }
193
+ });
194
+
195
+ // Check for bundle contamination errors
196
+ clientOnlyComponents.forEach(comp => {
197
+ if (serverExports.includes(comp.exportPath)) {
198
+ bundleErrors.server.push(comp.exportPath);
199
+ }
200
+ });
201
+
202
+ serverOnlyComponents.forEach(comp => {
203
+ if (clientExports.includes(comp.exportPath)) {
204
+ bundleErrors.client.push(comp.exportPath + ' (server-only component in client bundle)');
205
+ }
206
+ });
207
+
208
+ // Report results
209
+ console.log('📊 Analysis Results:');
210
+ console.log(` Found ${allComponentFiles.length} component files`);
211
+ console.log(` ${clientOnlyComponents.length} client-only components`);
212
+ console.log(` ${clientAndServerSafeComponents.length} client-and-server-safe components`);
213
+ console.log(` ${serverOnlyComponents.length} server-only components`);
214
+ console.log(` ${adminComponents.length} admin components`);
215
+ if (missing.server.length > 0) {
216
+ console.log('❌ Missing from server bundle (index.server.js):');
217
+ missing.server.forEach(path => console.log(` - ${path}`));
218
+ console.log('');
219
+ }
220
+
221
+ if (missing.client.length > 0) {
222
+ console.log('❌ Missing from client bundle (index.js):');
223
+ missing.client.forEach(path => console.log(` - ${path}`));
224
+ console.log('');
225
+ }
226
+
227
+ if (missing.adminServer.length > 0) {
228
+ console.log('❌ Missing from admin server bundle (index.adminserver.js):');
229
+ missing.adminServer.forEach(path => console.log(` - ${path}`));
230
+ console.log('');
231
+ }
232
+
233
+ if (missing.adminClient.length > 0) {
234
+ console.log('❌ Missing from admin client bundle (index.adminclient.js):');
235
+ missing.adminClient.forEach(path => console.log(` - ${path}`));
236
+ console.log('');
237
+ }
238
+
239
+ if (bundleErrors.server.length > 0) {
240
+ console.log('🚨 Bundle contamination errors:');
241
+ console.log(' Client-required components incorrectly exported from server bundle:');
242
+ bundleErrors.server.forEach(path => console.log(` - ${path}`));
243
+ console.log('');
244
+ }
245
+
246
+ if (bundleErrors.client.length > 0) {
247
+ console.log('🚨 Bundle contamination errors:');
248
+ console.log(' Server-required components incorrectly exported from client bundle:');
249
+ bundleErrors.client.forEach(path => console.log(` - ${path}`));
250
+ console.log('');
251
+ }
252
+
253
+ if (bundleErrors.adminServer.length > 0) {
254
+ console.log('🚨 Bundle contamination errors:');
255
+ console.log(' Client-required components incorrectly exported from admin server bundle:');
256
+ bundleErrors.adminServer.forEach(path => console.log(` - ${path}`));
257
+ console.log('');
258
+ }
259
+
260
+ if (bundleErrors.adminClient.length > 0) {
261
+ console.log('🚨 Bundle contamination errors:');
262
+ console.log(' Server-only components incorrectly exported from admin client bundle:');
263
+ bundleErrors.adminClient.forEach(path => console.log(` - ${path}`));
264
+ console.log('');
265
+ }
266
+
267
+ if (missing.files.length > 0) {
268
+ console.log('❌ Exported paths that don\'t exist:');
269
+ missing.files.forEach(path => console.log(` - ${path}`));
270
+ console.log('');
271
+ }
272
+
273
+ const hasErrors = missing.server.length > 0 || missing.client.length > 0 || missing.adminServer.length > 0 || missing.adminClient.length > 0 || bundleErrors.server.length > 0 || bundleErrors.client.length > 0 || bundleErrors.adminServer.length > 0 || bundleErrors.adminClient.length > 0 || missing.files.length > 0;
274
+
275
+ if (hasErrors) {
276
+ console.log('❌ Validation failed!');
277
+ process.exit(1);
278
+ } else {
279
+ console.log('✅ All exports validated successfully!');
280
+ }
@@ -0,0 +1,280 @@
1
+ import fs from 'fs';
2
+ import glob from 'glob';
3
+ import { CLIENT_ONLY_PATTERNS, TS_FILE_IGNORE_PATTERNS, TSX_FILE_IGNORE_PATTERNS, SERVER_ONLY_PATTERNS } from '../components/utilities/functions.ts';
4
+
5
+ console.log('🔍 Validating exports...\n');
6
+
7
+ // Find all .ts files (excluding .d.ts, test files, stories, examples, types.ts)
8
+ const tsFiles = glob.sync('src/components/**/*.ts', {
9
+ ignore: TS_FILE_IGNORE_PATTERNS
10
+ });
11
+
12
+ // Find all .tsx files (excluding test files, stories, examples)
13
+ const tsxFiles = glob.sync('src/components/**/*.tsx', {
14
+ ignore: TSX_FILE_IGNORE_PATTERNS
15
+ });
16
+
17
+ // Combine all component files
18
+ const allComponentFiles = [...tsFiles, ...tsxFiles];
19
+
20
+ // Analyze each component file to determine if it's client-required or server-safe
21
+ function analyzeComponentFile(filePath) {
22
+ const content = fs.readFileSync(filePath, 'utf8');
23
+
24
+ // Server-only patterns that indicate this should only be on server (not client)
25
+ // (Imported from shared utilities)
26
+
27
+ // Client-only patterns that require the component to run on client
28
+ // (Imported from shared utilities)
29
+
30
+ // Check if file contains any server-only patterns
31
+ const isServerOnly = SERVER_ONLY_PATTERNS.some(pattern => pattern.test(content));
32
+
33
+ // Check if file contains any client-only patterns
34
+ const isClientOnly = CLIENT_ONLY_PATTERNS.some(pattern => pattern.test(content));
35
+
36
+ return {
37
+ filePath,
38
+ isClientOnly,
39
+ isServerOnly,
40
+ exportPath: filePath.replace('src/', './').replace(/\.tsx?$/, '')
41
+ };
42
+ }
43
+
44
+ // Analyze all component files
45
+ const analyzedComponents = allComponentFiles.map(analyzeComponentFile);
46
+
47
+ // Special handling for utilities/functions.ts - it's safe for both client and server despite containing pattern definitions
48
+ analyzedComponents.forEach(comp => {
49
+ if (comp.exportPath === './components/utilities/functions') {
50
+ comp.isClientOnly = false;
51
+ comp.isServerOnly = false;
52
+ }
53
+ });
54
+
55
+ // Separate admin and non-admin components
56
+ const adminComponents = analyzedComponents.filter(comp => comp.exportPath.startsWith('./components/admin/'));
57
+ const nonAdminComponents = analyzedComponents.filter(comp => !comp.exportPath.startsWith('./components/admin/'));
58
+
59
+ // Create arrays of components for each bundle (non-admin only)
60
+ // If a component has server-only patterns, it's server-only; if client-only and not server-only, client-only; else safe
61
+ const serverOnlyComponents = nonAdminComponents.filter(comp => comp.isServerOnly);
62
+ const clientOnlyComponents = nonAdminComponents.filter(comp => comp.isClientOnly && !comp.isServerOnly);
63
+ const clientAndServerSafeComponents = nonAdminComponents.filter(comp => !comp.isClientOnly && !comp.isServerOnly);
64
+
65
+ // Read index files
66
+ const indexServer = fs.readFileSync('src/index.server.js', 'utf8');
67
+ const indexClient = fs.readFileSync('src/index.js', 'utf8');
68
+ const indexAdminServer = fs.readFileSync('src/index.adminserver.js', 'utf8');
69
+ const indexAdminClient = fs.readFileSync('src/index.adminclient.js', 'utf8');
70
+
71
+ // Helper function to extract exports from index file
72
+ function extractExports(content) {
73
+ // Remove comments
74
+ content = content.replace(/\/\*[\s\S]*?\*\//g, '');
75
+ content = content.replace(/\/\/.*$/gm, '');
76
+
77
+ const exports = [];
78
+
79
+ // Handle export * from './path' syntax
80
+ const exportAllRegex = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g;
81
+ let match;
82
+
83
+ while ((match = exportAllRegex.exec(content)) !== null) {
84
+ exports.push(match[1]);
85
+ }
86
+
87
+ // Handle export { ... } from './path' syntax
88
+ const exportRegex = /export\s+{\s*([^}]+)\s*}\s+from\s+['"]([^'"]+)['"]/g;
89
+ while ((match = exportRegex.exec(content)) !== null) {
90
+ const exportList = match[1];
91
+ const exportItems = exportList.split(',').map(item => item.trim());
92
+ exports.push(...exportItems.map(item => match[2])); // Use the from path
93
+ }
94
+
95
+ return exports;
96
+ }
97
+
98
+ // Extract exports from all index files
99
+ const serverExports = extractExports(indexServer);
100
+ const clientExports = extractExports(indexClient);
101
+ const adminServerExports = extractExports(indexAdminServer);
102
+ const adminClientExports = extractExports(indexAdminClient);
103
+
104
+ const missing = {
105
+ server: [],
106
+ client: [],
107
+ adminServer: [],
108
+ adminClient: [],
109
+ files: [] // New: exported paths that don't correspond to existing files
110
+ };
111
+
112
+ const bundleErrors = {
113
+ server: [], // Client-only components incorrectly in server bundle
114
+ client: [], // Server-only components incorrectly in client bundle
115
+ adminServer: [], // Client-only components incorrectly in admin server bundle
116
+ adminClient: [] // Server-only components incorrectly in admin client bundle
117
+ };
118
+
119
+ // Check if exported paths correspond to existing files
120
+ function checkExportPathsExist(exports, bundleName) {
121
+ exports.forEach(exportPath => {
122
+ // Convert export path to file path (./components/... -> src/components/...)
123
+ const filePathBase = exportPath.replace('./', 'src/');
124
+ const tsFile = `${filePathBase}.ts`;
125
+ const tsxFile = `${filePathBase}.tsx`;
126
+
127
+ const tsExists = fs.existsSync(tsFile);
128
+ const tsxExists = fs.existsSync(tsxFile);
129
+
130
+ if (!tsExists && !tsxExists) {
131
+ missing.files.push(`${bundleName}: ${exportPath} (.ts/.tsx files not found)`);
132
+ }
133
+ });
134
+ }
135
+
136
+ checkExportPathsExist(serverExports, 'server bundle');
137
+ checkExportPathsExist(clientExports, 'client bundle');
138
+ checkExportPathsExist(adminServerExports, 'admin server bundle');
139
+ checkExportPathsExist(adminClientExports, 'admin client bundle');
140
+
141
+ // Check for missing exports
142
+ clientAndServerSafeComponents.forEach(comp => {
143
+ if (!serverExports.includes(comp.exportPath)) {
144
+ missing.server.push(comp.exportPath);
145
+ }
146
+ });
147
+
148
+ serverOnlyComponents.forEach(comp => {
149
+ if (!serverExports.includes(comp.exportPath)) {
150
+ missing.server.push(comp.exportPath);
151
+ }
152
+ });
153
+
154
+ clientOnlyComponents.forEach(comp => {
155
+ if (!clientExports.includes(comp.exportPath)) {
156
+ missing.client.push(comp.exportPath);
157
+ }
158
+ });
159
+
160
+ clientAndServerSafeComponents.forEach(comp => {
161
+ if (!clientExports.includes(comp.exportPath)) {
162
+ missing.client.push(comp.exportPath);
163
+ }
164
+ });
165
+
166
+ // Check admin components - separate server and client bundles
167
+ const serverRelevantAdmin = adminComponents.filter(comp => comp.isServerOnly || !comp.isClientOnly);
168
+ const clientRelevantAdmin = adminComponents.filter(comp => comp.isClientOnly || !comp.isServerOnly);
169
+
170
+ serverRelevantAdmin.forEach(comp => {
171
+ if (!adminServerExports.includes(comp.exportPath)) {
172
+ missing.adminServer.push(comp.exportPath);
173
+ }
174
+ if (serverExports.includes(comp.exportPath) && !comp.isServerOnly) {
175
+ bundleErrors.server.push(comp.exportPath + ' (admin component in server bundle)');
176
+ }
177
+ if (clientExports.includes(comp.exportPath) && !comp.isServerOnly) {
178
+ bundleErrors.client.push(comp.exportPath + ' (admin component in client bundle)');
179
+ }
180
+ });
181
+
182
+ clientRelevantAdmin.forEach(comp => {
183
+ if (!adminClientExports.includes(comp.exportPath)) {
184
+ missing.adminClient.push(comp.exportPath);
185
+ }
186
+ if (serverExports.includes(comp.exportPath) && comp.isClientOnly) {
187
+ bundleErrors.server.push(comp.exportPath + ' (client-only admin component in server bundle)');
188
+ }
189
+ // Allow client-only components in adminserver if they are also server-only (both)
190
+ if (adminServerExports.includes(comp.exportPath) && comp.isClientOnly && !comp.isServerOnly) {
191
+ bundleErrors.adminServer.push(comp.exportPath + ' (client-only admin component in admin server bundle)');
192
+ }
193
+ });
194
+
195
+ // Check for bundle contamination errors
196
+ clientOnlyComponents.forEach(comp => {
197
+ if (serverExports.includes(comp.exportPath)) {
198
+ bundleErrors.server.push(comp.exportPath);
199
+ }
200
+ });
201
+
202
+ serverOnlyComponents.forEach(comp => {
203
+ if (clientExports.includes(comp.exportPath)) {
204
+ bundleErrors.client.push(comp.exportPath + ' (server-only component in client bundle)');
205
+ }
206
+ });
207
+
208
+ // Report results
209
+ console.log('📊 Analysis Results:');
210
+ console.log(` Found ${allComponentFiles.length} component files`);
211
+ console.log(` ${clientOnlyComponents.length} client-only components`);
212
+ console.log(` ${clientAndServerSafeComponents.length} client-and-server-safe components`);
213
+ console.log(` ${serverOnlyComponents.length} server-only components`);
214
+ console.log(` ${adminComponents.length} admin components`);
215
+ if (missing.server.length > 0) {
216
+ console.log('❌ Missing from server bundle (index.server.js):');
217
+ missing.server.forEach(path => console.log(` - ${path}`));
218
+ console.log('');
219
+ }
220
+
221
+ if (missing.client.length > 0) {
222
+ console.log('❌ Missing from client bundle (index.js):');
223
+ missing.client.forEach(path => console.log(` - ${path}`));
224
+ console.log('');
225
+ }
226
+
227
+ if (missing.adminServer.length > 0) {
228
+ console.log('❌ Missing from admin server bundle (index.adminserver.js):');
229
+ missing.adminServer.forEach(path => console.log(` - ${path}`));
230
+ console.log('');
231
+ }
232
+
233
+ if (missing.adminClient.length > 0) {
234
+ console.log('❌ Missing from admin client bundle (index.adminclient.js):');
235
+ missing.adminClient.forEach(path => console.log(` - ${path}`));
236
+ console.log('');
237
+ }
238
+
239
+ if (bundleErrors.server.length > 0) {
240
+ console.log('🚨 Bundle contamination errors:');
241
+ console.log(' Client-required components incorrectly exported from server bundle:');
242
+ bundleErrors.server.forEach(path => console.log(` - ${path}`));
243
+ console.log('');
244
+ }
245
+
246
+ if (bundleErrors.client.length > 0) {
247
+ console.log('🚨 Bundle contamination errors:');
248
+ console.log(' Server-required components incorrectly exported from client bundle:');
249
+ bundleErrors.client.forEach(path => console.log(` - ${path}`));
250
+ console.log('');
251
+ }
252
+
253
+ if (bundleErrors.adminServer.length > 0) {
254
+ console.log('🚨 Bundle contamination errors:');
255
+ console.log(' Client-required components incorrectly exported from admin server bundle:');
256
+ bundleErrors.adminServer.forEach(path => console.log(` - ${path}`));
257
+ console.log('');
258
+ }
259
+
260
+ if (bundleErrors.adminClient.length > 0) {
261
+ console.log('🚨 Bundle contamination errors:');
262
+ console.log(' Server-only components incorrectly exported from admin client bundle:');
263
+ bundleErrors.adminClient.forEach(path => console.log(` - ${path}`));
264
+ console.log('');
265
+ }
266
+
267
+ if (missing.files.length > 0) {
268
+ console.log('❌ Exported paths that don\'t exist:');
269
+ missing.files.forEach(path => console.log(` - ${path}`));
270
+ console.log('');
271
+ }
272
+
273
+ const hasErrors = missing.server.length > 0 || missing.client.length > 0 || missing.adminServer.length > 0 || missing.adminClient.length > 0 || bundleErrors.server.length > 0 || bundleErrors.client.length > 0 || bundleErrors.adminServer.length > 0 || bundleErrors.adminClient.length > 0 || missing.files.length > 0;
274
+
275
+ if (hasErrors) {
276
+ console.log('❌ Validation failed!');
277
+ process.exit(1);
278
+ } else {
279
+ console.log('✅ All exports validated successfully!');
280
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate-site-images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-site-images.d.ts","sourceRoot":"","sources":["../../../src/scripts/generate-site-images.js"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ declare namespace _default {
2
+ namespace meta {
3
+ let type: string;
4
+ namespace docs {
5
+ let description: string;
6
+ let category: string;
7
+ let recommended: boolean;
8
+ }
9
+ let fixable: boolean;
10
+ let schema: never[];
11
+ namespace messages {
12
+ let missingPropTypes: string;
13
+ let missingInferProps: string;
14
+ let invalidInferProps: string;
15
+ let missingInferPropsUsage: string;
16
+ let propTypesPlacement: string;
17
+ let inferPropsPlacement: string;
18
+ }
19
+ }
20
+ function create(context: any): {
21
+ FunctionDeclaration(node: any): void;
22
+ AssignmentExpression(node: any): void;
23
+ TSTypeAliasDeclaration(node: any): void;
24
+ 'FunctionDeclaration:exit'(node: any): void;
25
+ };
26
+ }
27
+ export default _default;
28
+ //# sourceMappingURL=proptypes-inferprops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proptypes-inferprops.d.ts","sourceRoot":"","sources":["../../../src/scripts/proptypes-inferprops.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;IAqBC;;;;;MAsLC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validate-exports.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-exports.d.cts","sourceRoot":"","sources":["../../../src/scripts/validate-exports.cjs"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validate-exports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-exports.d.ts","sourceRoot":"","sources":["../../../src/scripts/validate-exports.js"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelated-tech/components",
3
- "version": "3.7.0",
3
+ "version": "3.7.3",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "Pixelated Technologies",
@@ -39,7 +39,8 @@
39
39
  "default": "./dist/index.adminclient.js"
40
40
  },
41
41
  "./dist/components/*": "./dist/components/*",
42
- "./css/*": "./dist/css/*"
42
+ "./css/*": "./dist/css/*",
43
+ "./scripts/*": "./dist/scripts/*"
43
44
  },
44
45
  "homepage": "https://github.com/brianwhaley/pixelated-components#readme",
45
46
  "bugs": {
@@ -63,10 +64,10 @@
63
64
  "buildClean": "rm -rf dist",
64
65
  "tsc": "npx tsc --project tsconfig.json",
65
66
  "tscClean": "rm -rf dist/{images,stories,tests}",
66
- "rsync": "(cd src && tar -cf - $(find . -name \"*.css\" -o -name \"*.scss\" -o -name \"*.json\")) | tar -C dist -xf -",
67
- "rsync1": "find src -type f \\( -name \"*.css\" -o -name \"*.scss\" -o -name \"*.json\" \\) -exec sh -c 'mkdir -p dist/$(dirname \"${1#src/}\") && cp \"$1\" dist/\"${1#src/}\"' _ {} \\;",
68
- "rsync2": "rsync -a --include='*.css' --include='*.scss' --include='*.json' --include='*/' --exclude='*' src/ dist",
69
- "validate-exports": "node scripts/validate-exports.cjs",
67
+ "rsync": "(cd src && tar -cf - $(find . -name \"*.css\" -o -name \"*.scss\" -o -name \"*.json\") scripts/) | tar -C dist -xf -",
68
+ "rsync1": "find src -type f \\( -name \"*.css\" -o -name \"*.scss\" -o -name \"*.json\" \\) -exec sh -c 'mkdir -p dist/$(dirname \"${1#src/}\") && cp \"$1\" dist/\"${1#src/}\"' _ {} \\; ; find src/scripts -type f -exec sh -c 'mkdir -p dist/$(dirname \"${1#src/}\") && cp \"$1\" dist/\"${1#src/}\"' _ {} \\;",
69
+ "rsync2": "rsync -a --include='*.css' --include='*.scss' --include='*.json' --include='scripts/**' --include='*/' --exclude='*' src/ dist",
70
+ "validate-exports": "npx tsx src/scripts/validate-exports.js",
70
71
  "copy2brianwhaley": "rm -rf ../brianwhaley/node_modules/@pixelated-tech/components/dist && cp -r dist ../brianwhaley/node_modules/@pixelated-tech/components/",
71
72
  "copy2informationfocus": "rm -rf ../informationfocus/node_modules/@pixelated-tech/components/dist && cp -r dist ../informationfocus/node_modules/@pixelated-tech/components/",
72
73
  "copy2oaktreelandscaping": "rm -rf ../oaktreelandscaping/node_modules/@pixelated-tech/components/dist && cp -r dist ../oaktreelandscaping/node_modules/@pixelated-tech/components/",
@@ -84,7 +85,7 @@
84
85
  "test:coverage": "vitest run --coverage",
85
86
  "test:run": "vitest run",
86
87
  "lint": "eslint --fix",
87
- "release": "./scripts/release.sh"
88
+ "release": "./src/scripts/release.sh"
88
89
  },
89
90
  "scripts-old": {
90
91
  "buildBabel": "npm run buildClean && NODE_ENV=production babel src --out-dir dist --copy-files",
@@ -1 +0,0 @@
1
- "use strict";
@@ -1,206 +0,0 @@
1
- import { http, HttpResponse } from 'msw';
2
- console.log('MSW handlers loaded');
3
- // Mock data for Axe Core accessibility
4
- const mockAxeData = {
5
- success: true,
6
- data: {
7
- site: 'example.com',
8
- url: 'https://example.com',
9
- violations: [
10
- {
11
- id: 'color-contrast',
12
- impact: 'serious',
13
- description: 'Elements must have sufficient color contrast',
14
- help: 'Ensure text has enough contrast against background',
15
- helpUrl: 'https://dequeuniversity.com/rules/axe/4.8/color-contrast',
16
- nodes: [
17
- {
18
- target: ['.header h1'],
19
- html: '<h1>Welcome</h1>',
20
- failureSummary: 'Fix any of the following: Element has insufficient color contrast of 2.5:1'
21
- }
22
- ]
23
- },
24
- {
25
- id: 'image-alt',
26
- impact: 'critical',
27
- description: 'Images must have alternate text',
28
- help: 'Provide alternative text for images',
29
- helpUrl: 'https://dequeuniversity.com/rules/axe/4.8/image-alt',
30
- nodes: [
31
- {
32
- target: ['img[alt=""]'],
33
- html: '<img src="logo.png" alt="">',
34
- failureSummary: 'Fix any of the following: aria-label attribute does not exist or is empty'
35
- }
36
- ]
37
- }
38
- ],
39
- passes: 45,
40
- incomplete: 2,
41
- inapplicable: 12
42
- }
43
- };
44
- // Mock data for Core Web Vitals
45
- const mockCoreWebVitalsData = {
46
- success: true,
47
- data: {
48
- site: 'pixelated.tech',
49
- url: 'https://pixelated.tech',
50
- metrics: {
51
- cls: 0.05,
52
- fid: 85,
53
- lcp: 1200,
54
- fcp: 800,
55
- ttfb: 150,
56
- speedIndex: 1100,
57
- interactive: 1300,
58
- totalBlockingTime: 50,
59
- firstMeaningfulPaint: 900
60
- },
61
- scores: {
62
- performance: 85,
63
- accessibility: 90,
64
- bestPractices: 95,
65
- seo: 88,
66
- pwa: 75
67
- },
68
- categories: {
69
- performance: { score: 85, displayValue: 'Good' },
70
- accessibility: { score: 90, displayValue: 'Good' },
71
- bestPractices: { score: 95, displayValue: 'Good' },
72
- seo: { score: 88, displayValue: 'Good' },
73
- pwa: { score: 75, displayValue: 'Good' }
74
- }
75
- }
76
- };
77
- // Mock data for Google Analytics
78
- const mockGoogleAnalyticsData = {
79
- success: true,
80
- data: {
81
- site: 'pixelated.tech',
82
- dateRange: {
83
- start: '2024-01-01',
84
- end: '2024-01-06'
85
- },
86
- metrics: {
87
- users: 1250,
88
- sessions: 1800,
89
- pageviews: 3200,
90
- bounceRate: 0.45,
91
- avgSessionDuration: 180,
92
- newUsers: 890
93
- },
94
- chartData: [
95
- { date: '2024-01-01', users: 120, sessions: 150, pageviews: 280 },
96
- { date: '2024-01-02', users: 135, sessions: 165, pageviews: 310 },
97
- { date: '2024-01-03', users: 142, sessions: 178, pageviews: 325 },
98
- { date: '2024-01-04', users: 158, sessions: 192, pageviews: 345 },
99
- { date: '2024-01-05', users: 165, sessions: 198, pageviews: 365 },
100
- { date: '2024-01-06', users: 172, sessions: 205, pageviews: 380 }
101
- ]
102
- }
103
- };
104
- // Mock data for Google Search Console
105
- const mockGoogleSearchConsoleData = {
106
- success: true,
107
- data: {
108
- site: 'pixelated.tech',
109
- dateRange: {
110
- start: '2024-01-01',
111
- end: '2024-01-06'
112
- },
113
- metrics: {
114
- clicks: 1250,
115
- impressions: 15000,
116
- ctr: 0.083,
117
- position: 12.5
118
- },
119
- chartData: [
120
- { date: '2024-01-01', clicks: 85, impressions: 1200, ctr: 0.071, position: 14.2 },
121
- { date: '2024-01-02', clicks: 92, impressions: 1350, ctr: 0.068, position: 13.8 },
122
- { date: '2024-01-03', clicks: 98, impressions: 1420, ctr: 0.069, position: 13.5 },
123
- { date: '2024-01-04', clicks: 105, impressions: 1480, ctr: 0.071, position: 13.2 },
124
- { date: '2024-01-05', clicks: 112, impressions: 1520, ctr: 0.074, position: 12.8 },
125
- { date: '2024-01-06', clicks: 118, impressions: 1580, ctr: 0.075, position: 12.5 }
126
- ]
127
- }
128
- };
129
- // Mock data for CloudWatch uptime
130
- const mockCloudWatchData = {
131
- success: true,
132
- data: {
133
- site: 'pixelated.tech',
134
- uptime: 99.8,
135
- responseTime: 245,
136
- status: 'operational',
137
- incidents: [],
138
- chartData: [
139
- { timestamp: '2024-01-01T00:00:00Z', uptime: 100, responseTime: 220 },
140
- { timestamp: '2024-01-02T00:00:00Z', uptime: 99.5, responseTime: 280 },
141
- { timestamp: '2024-01-03T00:00:00Z', uptime: 100, responseTime: 210 },
142
- { timestamp: '2024-01-04T00:00:00Z', uptime: 100, responseTime: 235 },
143
- { timestamp: '2024-01-05T00:00:00Z', uptime: 99.9, responseTime: 250 },
144
- { timestamp: '2024-01-06T00:00:00Z', uptime: 100, responseTime: 225 }
145
- ]
146
- }
147
- };
148
- export const handlers = [
149
- // API endpoints using relative paths (MSW v2 matches these regardless of origin)
150
- http.get('/api/site-health/axe-core', () => {
151
- console.log('MSW: Intercepting axe-core request');
152
- return HttpResponse.json(mockAxeData);
153
- }),
154
- http.get('/api/site-health/core-web-vitals', () => {
155
- console.log('MSW: Intercepting core-web-vitals request');
156
- return HttpResponse.json(mockCoreWebVitalsData);
157
- }),
158
- http.get('/api/site-health/google-analytics', () => {
159
- console.log('MSW: Intercepting google-analytics request');
160
- return HttpResponse.json(mockGoogleAnalyticsData);
161
- }),
162
- http.get('/api/site-health/google-search-console', () => {
163
- console.log('MSW: Intercepting google-search-console request');
164
- return HttpResponse.json(mockGoogleSearchConsoleData);
165
- }),
166
- http.get('/api/site-health/cloudwatch', () => {
167
- console.log('MSW: Intercepting cloudwatch request');
168
- return HttpResponse.json(mockCloudWatchData);
169
- }),
170
- // External URLs used by some stories
171
- http.get('https://api.pixelated.tech/axe-core', () => {
172
- console.log('MSW: Intercepting external axe-core request');
173
- return HttpResponse.json(mockAxeData);
174
- }),
175
- http.get('https://api.pixelated.tech/core-web-vitals', () => {
176
- console.log('MSW: Intercepting external core-web-vitals request');
177
- return HttpResponse.json(mockCoreWebVitalsData);
178
- }),
179
- http.get('https://api.pixelated.tech/google-analytics', () => {
180
- console.log('MSW: Intercepting external google-analytics request');
181
- return HttpResponse.json(mockGoogleAnalyticsData);
182
- }),
183
- http.get('https://api.pixelated.tech/google-search-console', () => {
184
- console.log('MSW: Intercepting external google-search-console request');
185
- return HttpResponse.json(mockGoogleSearchConsoleData);
186
- }),
187
- http.get('https://api.pixelated.tech/cloudwatch', () => {
188
- console.log('MSW: Intercepting external cloudwatch request');
189
- return HttpResponse.json(mockCloudWatchData);
190
- }),
191
- http.get('https://api.pixelated.tech/template-data', () => {
192
- console.log('MSW: Intercepting template-data request');
193
- return HttpResponse.json({
194
- success: true,
195
- data: { message: 'Mock template data' }
196
- });
197
- }),
198
- // Generic fallback for any other site health endpoints
199
- http.get('/api/site-health/*', () => {
200
- console.log('MSW: Intercepting generic site-health request');
201
- return HttpResponse.json({
202
- success: true,
203
- data: { message: 'Mock data for site health endpoint' }
204
- });
205
- })
206
- ];
@@ -1 +0,0 @@
1
- "use strict";
@@ -1 +0,0 @@
1
- //# sourceMappingURL=browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/mocks/browser.js"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export const handlers: any[];
2
- //# sourceMappingURL=handlers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/mocks/handlers.js"],"names":[],"mappings":"AA0JA,6BAqEE"}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mocks/index.js"],"names":[],"mappings":""}