Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Starknet Configuration
NEXT_PUBLIC_STARKNET_NETWORK=goerli-alpha
# NEXT_PUBLIC_STARKNET_RPC_URL=https://your-rpc-endpoint.com

# Optional: For production deployments
# NEXT_PUBLIC_STARKNET_NETWORK=mainnet-alpha
33 changes: 32 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,29 @@ jobs:
- name: Run Lint
run: npm run lint

validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Validate UI (icons, responsive)
run: npm run validate:ui

- name: Validate Web3 (wallet, env)
run: npm run validate:web3

build:
runs-on: ubuntu-latest
needs: [type-check, lint]
needs: [type-check, lint, validate]
steps:
- uses: actions/checkout@v4

Expand All @@ -58,6 +78,17 @@ jobs:

- name: Run Build
run: npm run build
env:
NEXT_PUBLIC_STARKNET_NETWORK: goerli-alpha

- name: Check for build errors
run: |
if [ -f .next/build-manifest.json ]; then
echo "✅ Build completed successfully"
else
echo "❌ Build failed - no manifest found"
exit 1
fi

test:
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui",
"test:watch": "vitest --watch",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"validate:ui": "node scripts/validate-ui.js",
"validate:web3": "node scripts/validate-web3.js",
"validate": "npm run validate:ui && npm run validate:web3"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
Expand Down
131 changes: 131 additions & 0 deletions scripts/validate-ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env node

/**
* UI Validation Script
* Checks for consistent icon usage and responsive Tailwind classes
* Exit code 0 = pass, 1 = fail
*/

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const SRC_DIR = path.join(__dirname, '../src');
const COMPONENT_DIRS = ['components', 'app', 'pages'];

// Disallowed icon libraries (should use lucide-react)
const DISALLOWED_ICONS = [
{ pattern: /from ['"]@heroicons\/react/g, name: '@heroicons/react' },
{ pattern: /from ['"]@fortawesome/g, name: '@fortawesome' },
{ pattern: /from ['"]react-feather/g, name: 'react-feather' },
];

// Required responsive breakpoints for key layout patterns
const RESPONSIVE_PATTERNS = [
{ pattern: /\bflex\b/, shouldHave: ['sm:', 'md:', 'lg:'], context: 'flex layouts' },
{ pattern: /\bgrid\b/, shouldHave: ['sm:', 'md:', 'lg:'], context: 'grid layouts' },
];

let errors = [];
let warnings = [];

function getAllFiles(dir, extensions = ['.tsx', '.jsx', '.ts', '.js']) {
let files = [];

if (!fs.existsSync(dir)) return files;

const items = fs.readdirSync(dir);

for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);

if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
files = files.concat(getAllFiles(fullPath, extensions));
} else if (stat.isFile() && extensions.some(ext => item.endsWith(ext))) {
files.push(fullPath);
}
}

return files;
}

function checkIconUsage(content, filePath) {
for (const { pattern, name } of DISALLOWED_ICONS) {
if (pattern.test(content)) {
errors.push(`[ICON] ${filePath}: Uses ${name} - should use lucide-react`);
}
}

// Check for react-icons usage (warning, not error)
if (/from ['"]react-icons/g.test(content)) {
warnings.push(`[ICON] ${filePath}: Uses react-icons - prefer lucide-react for consistency`);
}
}

function checkResponsiveTailwind(content, filePath) {
// Only check component files that have className
if (!content.includes('className')) return;

// Check for common layout patterns without responsive variants
const lines = content.split('\n');

lines.forEach((line, index) => {
// Check for grid/flex without any responsive classes
if (/className=["'][^"']*\b(grid|flex)\b[^"']*["']/.test(line)) {
const hasResponsive = /\b(sm|md|lg|xl|2xl):/.test(line);
if (!hasResponsive && line.includes('grid-cols-') && !line.includes('grid-cols-1')) {
warnings.push(`[RESPONSIVE] ${filePath}:${index + 1}: Grid layout may need responsive classes`);
}
}
});
}

function checkForConsoleStatements(content, filePath) {
// Allow console.warn and console.error, flag console.log
const matches = content.match(/console\.log\(/g);
if (matches && matches.length > 0) {
warnings.push(`[CONSOLE] ${filePath}: Contains ${matches.length} console.log statement(s)`);
}
}

function validateFiles() {
console.log('🔍 Running UI validation checks...\n');

for (const dir of COMPONENT_DIRS) {
const fullDir = path.join(SRC_DIR, dir);
const files = getAllFiles(fullDir);

for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
const relativePath = path.relative(process.cwd(), file);

checkIconUsage(content, relativePath);
checkResponsiveTailwind(content, relativePath);
checkForConsoleStatements(content, relativePath);
}
}
}

function printResults() {
if (warnings.length > 0) {
console.log('⚠️ Warnings:\n');
warnings.forEach(w => console.log(` ${w}`));
console.log('');
}

if (errors.length > 0) {
console.log('❌ Errors:\n');
errors.forEach(e => console.log(` ${e}`));
console.log('');
console.log(`\n❌ UI validation failed with ${errors.length} error(s)`);
process.exit(1);
}

console.log(`✅ UI validation passed (${warnings.length} warning(s))`);
process.exit(0);
}

// Run validation
validateFiles();
printResults();
104 changes: 104 additions & 0 deletions scripts/validate-web3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env node

/**
* Web3 Validation Script
* Validates wallet provider setup and environment configuration
* Exit code 0 = pass, 1 = fail
*/

const fs = require('fs');
const path = require('path');

const SRC_DIR = path.join(__dirname, '../src');

let errors = [];
let warnings = [];

function checkWalletProviderExists() {
const providerPath = path.join(SRC_DIR, 'providers/WalletProvider.tsx');

if (!fs.existsSync(providerPath)) {
errors.push('[WEB3] WalletProvider.tsx not found in src/providers/');
return false;
}

const content = fs.readFileSync(providerPath, 'utf-8');

// Check for error handling
if (!content.includes('try') || !content.includes('catch')) {
errors.push('[WEB3] WalletProvider should have try-catch error handling');
}

// Check for SSR safety
if (!content.includes("typeof window")) {
warnings.push('[WEB3] WalletProvider should check for SSR (typeof window)');
}

// Check for graceful fallback
if (!content.includes('useContext') || !content.includes('null')) {
warnings.push('[WEB3] useWallet hook should handle missing provider gracefully');
}

return true;
}

function checkWeb3Utils() {
const utilsPath = path.join(SRC_DIR, 'utils/web3/index.ts');

if (!fs.existsSync(utilsPath)) {
errors.push('[WEB3] utils/web3/index.ts not found');
return false;
}

const envValidationPath = path.join(SRC_DIR, 'utils/web3/envValidation.ts');
if (!fs.existsSync(envValidationPath)) {
errors.push('[WEB3] utils/web3/envValidation.ts not found');
return false;
}

const content = fs.readFileSync(envValidationPath, 'utf-8');

// Check for network validation
if (!content.includes('NEXT_PUBLIC_STARKNET')) {
warnings.push('[WEB3] Environment validation should check NEXT_PUBLIC_STARKNET_* vars');
}

return true;
}

function checkEnvExample() {
const envExamplePath = path.join(__dirname, '../.env.example');
const envLocalPath = path.join(__dirname, '../.env.local.example');

if (!fs.existsSync(envExamplePath) && !fs.existsSync(envLocalPath)) {
warnings.push('[WEB3] Consider adding .env.example with NEXT_PUBLIC_STARKNET_* variables');
}
}

function printResults() {
console.log('🔍 Running Web3 validation checks...\n');

checkWalletProviderExists();
checkWeb3Utils();
checkEnvExample();

if (warnings.length > 0) {
console.log('⚠️ Warnings:\n');
warnings.forEach(w => console.log(` ${w}`));
console.log('');
}

if (errors.length > 0) {
console.log('❌ Errors:\n');
errors.forEach(e => console.log(` ${e}`));
console.log('');
console.log(`\n❌ Web3 validation failed with ${errors.length} error(s)`);
process.exit(1);
}

console.log(`✅ Web3 validation passed (${warnings.length} warning(s))`);
process.exit(0);
}

// Run validation
printResults();
Loading
Loading