@pixelated-tech/components 3.7.0 → 3.7.1
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/dist/scripts/generate-site-images.js +31 -0
- package/dist/scripts/proptypes-inferprops.js +205 -0
- package/dist/scripts/release.sh +194 -0
- package/dist/scripts/validate-exports.js +280 -0
- package/dist/types/scripts/generate-site-images.d.ts +2 -0
- package/dist/types/scripts/generate-site-images.d.ts.map +1 -0
- package/dist/types/scripts/proptypes-inferprops.d.ts +28 -0
- package/dist/types/scripts/proptypes-inferprops.d.ts.map +1 -0
- package/dist/types/scripts/validate-exports.d.ts +2 -0
- package/dist/types/scripts/validate-exports.d.ts.map +1 -0
- package/package.json +8 -7
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const publicDir = path.join(__dirname, '..', 'public');
|
|
6
|
+
const outFile = path.join(publicDir, 'site-images.json');
|
|
7
|
+
|
|
8
|
+
function listFiles(dir, exts = ['.jpg','.jpeg','.png','.webp','.avif','.gif']) {
|
|
9
|
+
const out = [];
|
|
10
|
+
if (!fs.existsSync(dir)) return out;
|
|
11
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
12
|
+
for (const it of items) {
|
|
13
|
+
const full = path.join(dir, it.name);
|
|
14
|
+
if (it.isDirectory()) out.push(...listFiles(full, exts));
|
|
15
|
+
else if (exts.includes(path.extname(it.name).toLowerCase())) {
|
|
16
|
+
out.push(path.relative(publicDir, full).replace(/\\\\/g, '/'));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const files = listFiles(publicDir);
|
|
23
|
+
const manifest = {
|
|
24
|
+
metadata: {
|
|
25
|
+
generatedAt: new Date().toISOString(),
|
|
26
|
+
imageCount: files.length,
|
|
27
|
+
},
|
|
28
|
+
images: files,
|
|
29
|
+
};
|
|
30
|
+
fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2), 'utf8');
|
|
31
|
+
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.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 @@
|
|
|
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 @@
|
|
|
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.
|
|
3
|
+
"version": "3.7.1",
|
|
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": "
|
|
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",
|