* feat: add README for @cio/db package and remove existingAccounts.json * load session, load profile and organization, add create profile hook * chore: cache session request The trick was passing all the cookies to the backend. Previously I was only passing the better-auth.session_token and not better-auth.session_data. Read about the difference here https://www.better-auth.com/docs/concepts/cookies#custom-cookies * feat: login, logout, onboarding, verify email, reset password. * feat: complete change email and email verification * feat: standardise how we make rpc calls and create org endpoints * fix ui errors * fix: handle potential undefined customerId and standardize avatar URL property * refactor: standardize custom domain handling and update telegram chat ID references * chore: add tailwind prefix to @cio/ui this fixed the media query issues because @cio/ui classes were overriding this dashboard tailwind build * chore: clean up org sidebar * chore: persist mode state * feat: implement order breadcrumbs * chore: upgrade to better auth 1.4 * chore: address common auth flow bugs and setup app for others to setup locally.
334 lines
12 KiB
JavaScript
Executable File
334 lines
12 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Script to add 'ui:' prefix to Tailwind classes in packages/ui
|
|
* Usage: node add-ui-prefix.cjs [--dry-run]
|
|
*
|
|
* This script will:
|
|
* - Find all .svelte, .ts, .tsx, .js, .jsx files in packages/ui/src
|
|
* - Add 'ui:' prefix to Tailwind utility classes
|
|
* - Skip classes that already have the prefix
|
|
* - Preserve existing code structure
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const DRY_RUN = process.argv.includes('--dry-run');
|
|
const UI_PREFIX = 'ui:';
|
|
|
|
// Check if a class looks like a Tailwind utility class
|
|
function isTailwindClass(className) {
|
|
// Skip if already has ui: prefix
|
|
if (className.startsWith(UI_PREFIX)) {
|
|
return false;
|
|
}
|
|
|
|
// Skip if it's clearly not a Tailwind class (contains special chars that aren't Tailwind)
|
|
// Allow brackets for arbitrary values like w-[100px] and colons for variant prefixes like after:
|
|
if (/[=;{}]/.test(className) && !className.includes('[') && !className.includes(']')) {
|
|
return false;
|
|
}
|
|
|
|
// Common Tailwind patterns
|
|
const patterns = [
|
|
// Variant prefixes (must come first to catch all variant combinations)
|
|
// This catches any class that starts with a variant prefix followed by a colon
|
|
/^[a-z-]+(\[.*?\])?(\/.*?)?:/,
|
|
// Layout & Display (including compound classes like inline-flex)
|
|
/^(flex|grid|block|inline|hidden|absolute|relative|fixed|sticky|contents|list-item|table|table-row|table-cell|inline-flex|inline-grid|inline-block|inline-table)$/,
|
|
// Flexbox utilities
|
|
/^(shrink|grow|flex-|basis-)/,
|
|
// Spacing (m-, p-, gap-, space-, inset-)
|
|
/^(m|p|mx|my|mt|mr|mb|ml|px|py|pt|pr|pb|pl|gap|space-[xy]|inset)-/,
|
|
// Negative spacing (e.g., -inset-2, -top-1)
|
|
/^-\w+/,
|
|
// Sizing (w-, h-, size-, min-, max-)
|
|
/^(w|h|min-w|min-h|max-w|max-h|size)-/,
|
|
// Typography
|
|
/^(text|font|leading|tracking|text-|font-)/,
|
|
// Colors
|
|
/^(bg|text|border|ring|outline|divide|from|via|to|fill|stroke)-/,
|
|
// Effects
|
|
/^(opacity|shadow|blur|brightness|contrast|grayscale|hue-rotate|invert|saturate|sepia|backdrop-)/,
|
|
// Transforms
|
|
/^(scale|rotate|translate|skew|transform|origin)-/,
|
|
// Transitions
|
|
/^(transition|duration|ease|delay|animate)-/,
|
|
// Interactivity (specific variants)
|
|
/^(hover|focus|active|disabled|group|peer|aria-|data-):/,
|
|
// Responsive
|
|
/^(sm|md|lg|xl|2xl):/,
|
|
// Dark mode
|
|
/^dark:/,
|
|
// Arbitrary values (must come before general patterns)
|
|
/^[a-z]+-\[/,
|
|
// Arbitrary variants (like [&_svg]:pointer-events-none, after:absolute, etc.)
|
|
/^\[.*\]:|^(after|before|first|last|only|nth|marker|placeholder|selection|backdrop):/,
|
|
// Common utilities
|
|
/^(rounded|border|outline|shadow|ring|overflow|z-|order-|col-|row-|justify-|items-|content-|self-|place-|object-|cursor-|select-|resize-|appearance-|pointer-events-|visibility-|whitespace-|break-|overflow-|overscroll-|scroll-|will-change-|transform|transition|top|right|bottom|left)/
|
|
];
|
|
|
|
return patterns.some((pattern) => pattern.test(className));
|
|
}
|
|
|
|
// Process a single class string
|
|
function processClassString(classString) {
|
|
// Split by whitespace and process each class
|
|
const classes = classString.split(/\s+/).filter((c) => c.trim());
|
|
const processed = classes.map((cls) => {
|
|
if (isTailwindClass(cls)) {
|
|
return `${UI_PREFIX}${cls}`;
|
|
}
|
|
return cls;
|
|
});
|
|
|
|
return processed.join(' ');
|
|
}
|
|
|
|
// Process file content
|
|
function processFileContent(content) {
|
|
let modified = content;
|
|
let changes = 0;
|
|
|
|
// Helper to process and count changes
|
|
function processAndReplace(pattern, replacer) {
|
|
modified = modified.replace(pattern, (match, ...args) => {
|
|
const result = replacer(match, ...args);
|
|
if (result !== match) {
|
|
changes++;
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
// Pattern 1: class="..." or className="..."
|
|
processAndReplace(/(class|className)="([^"]*)"/g, (match, attr, classes) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${attr}="${processed}"` : match;
|
|
});
|
|
|
|
// Pattern 1b: class='...' or className='...'
|
|
processAndReplace(/(class|className)='([^']*)'/g, (match, attr, classes) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${attr}='${processed}'` : match;
|
|
});
|
|
|
|
// Pattern 2: class={`...`} or className={`...`}
|
|
processAndReplace(/(class|className)=\{`([^`]+)`\}/g, (match, attr, classes) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${attr}={\`${processed}\`}` : match;
|
|
});
|
|
|
|
// Pattern 3: class={'...'} or className={'...'}
|
|
processAndReplace(/(class|className)=\{['"]([^'"]+)['"]\}/g, (match, attr, classes) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${attr}={\`${processed}\`}` : match;
|
|
});
|
|
|
|
// Pattern 4: DISABLED - Too broad, was catching JavaScript object properties
|
|
// Pattern 4: String literals in object properties: base: "...", default: '...', etc.
|
|
// This handles tv() calls and similar object literals
|
|
// Match any property followed by a string that looks like it contains Tailwind classes
|
|
// DISABLED: This was catching JavaScript object properties incorrectly
|
|
// processAndReplace(/(\w+)\s*:\s*["']([^"']+)["']/g, (match, key, value) => {
|
|
// // Check if value contains Tailwind classes
|
|
// // Skip if it's clearly not a class string (contains =, :, ; that aren't part of Tailwind syntax)
|
|
// if (/[=:;]/.test(value) && !value.includes('[') && !value.includes(']')) {
|
|
// return match;
|
|
// }
|
|
|
|
// const classes = value.split(/\s+/).filter((c) => c.trim());
|
|
// const hasTailwind = classes.some((c) => isTailwindClass(c));
|
|
|
|
// if (hasTailwind) {
|
|
// const processed = processClassString(value);
|
|
// // Preserve original quote style
|
|
// const quote = match.includes("'") ? "'" : '"';
|
|
// return processed !== value ? `${key}: ${quote}${processed}${quote}` : match;
|
|
// }
|
|
// return match;
|
|
// });
|
|
|
|
// Pattern 5: DISABLED - Too broad, was catching JavaScript object properties
|
|
// Pattern 5: Template literals in object properties: base: `...`
|
|
// DISABLED: This was catching JavaScript object properties incorrectly
|
|
// processAndReplace(/(\w+)\s*:\s*`([^`]+)`/g, (match, key, value) => {
|
|
// // Similar check as above
|
|
// if (/[=:;]/.test(value) && !value.includes('[') && !value.includes(']')) {
|
|
// return match;
|
|
// }
|
|
|
|
// const classes = value.split(/\s+/).filter((c) => c.trim());
|
|
// const hasTailwind = classes.some((c) => isTailwindClass(c));
|
|
|
|
// if (hasTailwind) {
|
|
// const processed = processClassString(value);
|
|
// return processed !== value ? `${key}: \`${processed}\`` : match;
|
|
// }
|
|
// return match;
|
|
// });
|
|
|
|
// Pattern 6: Simple string literals in function calls: cn("..."), tv({...})
|
|
// Only process specific functions that are known to contain Tailwind classes
|
|
// This pattern matches strings that are the first argument and have no complex expressions
|
|
const tailwindFunctionNames = ['cn', 'tv', 'clsx', 'classNames'];
|
|
processAndReplace(
|
|
new RegExp(`(${tailwindFunctionNames.join('|')})\\s*\\(\\s*['"]([^'"]+)['"]\\s*([,)])`, 'g'),
|
|
(match, fn, classes, separator) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${fn}("${processed}"${separator}` : match;
|
|
}
|
|
);
|
|
|
|
// Pattern 7: Template literals in function calls: cn(`...`)
|
|
processAndReplace(/(cn|tv)\s*\(`([^`]+)`/g, (match, fn, classes) => {
|
|
const processed = processClassString(classes);
|
|
return processed !== classes ? `${fn}(\`${processed}\`` : match;
|
|
});
|
|
|
|
// Pattern 9: tv() function calls with object properties containing Tailwind classes
|
|
// This specifically targets tv() calls and their variant/base properties
|
|
processAndReplace(/tv\s*\(\s*\{([\s\S]*?)\}\s*\)/g, (match, objContent) => {
|
|
// Process all string literals in the tv() object, regardless of nesting level
|
|
let processedObj = objContent;
|
|
|
|
// Process double-quoted strings
|
|
processedObj = processedObj.replace(/"([^"]*)"/g, (match, value) => {
|
|
const processed = processClassString(value);
|
|
return processed !== value ? `"${processed}"` : match;
|
|
});
|
|
|
|
// Process single-quoted strings
|
|
processedObj = processedObj.replace(/'([^']*)'/g, (match, value) => {
|
|
const processed = processClassString(value);
|
|
return processed !== value ? `'${processed}'` : match;
|
|
});
|
|
|
|
// Process template literals
|
|
processedObj = processedObj.replace(/`([^`]*)`/g, (match, value) => {
|
|
const processed = processClassString(value);
|
|
return processed !== value ? `\`${processed}\`` : match;
|
|
});
|
|
|
|
return `tv({${processedObj}})`;
|
|
});
|
|
|
|
// Pattern 8: class: cn(...) patterns (like in derived props)
|
|
// Matches: class: cn('string1', 'string2', ...)
|
|
processAndReplace(/class:\s*cn\s*\(([^)]+)\)/g, (match, args) => {
|
|
// Use a more sophisticated approach to handle multi-line strings and comments
|
|
let processedArgs = args;
|
|
|
|
// Process single-quoted strings: 'content'
|
|
processedArgs = processedArgs.replace(/'([^']*)'/g, (match, content) => {
|
|
const processed = processClassString(content);
|
|
return processed !== content ? `'${processed}'` : match;
|
|
});
|
|
|
|
// Process double-quoted strings: "content"
|
|
processedArgs = processedArgs.replace(/"([^"]*)"/g, (match, content) => {
|
|
const processed = processClassString(content);
|
|
return processed !== content ? `"${processed}"` : match;
|
|
});
|
|
|
|
return `class: cn(${processedArgs})`;
|
|
});
|
|
|
|
return { content: modified, changes };
|
|
}
|
|
|
|
// Process a single file
|
|
function processFile(filePath) {
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const { content: modified, changes } = processFileContent(content);
|
|
|
|
if (changes > 0) {
|
|
if (DRY_RUN) {
|
|
console.log(`[DRY RUN] Would update ${filePath} (${changes} changes)`);
|
|
} else {
|
|
fs.writeFileSync(filePath, modified, 'utf8');
|
|
console.log(`✓ Updated ${filePath} (${changes} changes)`);
|
|
}
|
|
return changes;
|
|
}
|
|
return 0;
|
|
} catch (error) {
|
|
console.error(`✗ Error processing ${filePath}:`, error.message);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Recursively find all relevant files
|
|
function findFiles(dir, extensions = ['.svelte', '.ts', '.tsx', '.js', '.jsx']) {
|
|
const files = [];
|
|
|
|
// Files to exclude
|
|
const excludedFiles = ['output.css'];
|
|
|
|
try {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
|
|
// Skip node_modules and other ignored directories
|
|
if (entry.isDirectory()) {
|
|
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
continue;
|
|
}
|
|
files.push(...findFiles(fullPath, extensions));
|
|
} else if (entry.isFile()) {
|
|
// Skip excluded files
|
|
if (excludedFiles.includes(entry.name)) {
|
|
continue;
|
|
}
|
|
|
|
const ext = path.extname(entry.name);
|
|
if (extensions.includes(ext)) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error reading directory ${dir}:`, error.message);
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
// Main execution
|
|
function main() {
|
|
const uiDir = path.join(__dirname);
|
|
const srcDir = path.join(uiDir, 'src');
|
|
|
|
if (!fs.existsSync(srcDir)) {
|
|
console.error(`Error: ${srcDir} does not exist`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Searching for files in ${srcDir}...`);
|
|
const files = findFiles(srcDir);
|
|
console.log(`Found ${files.length} files to process\n`);
|
|
|
|
if (DRY_RUN) {
|
|
console.log('DRY RUN MODE - No files will be modified\n');
|
|
}
|
|
|
|
let totalChanges = 0;
|
|
let filesChanged = 0;
|
|
|
|
for (const file of files) {
|
|
const changes = processFile(file);
|
|
if (changes > 0) {
|
|
filesChanged++;
|
|
totalChanges += changes;
|
|
}
|
|
}
|
|
|
|
console.log(`\n${DRY_RUN ? 'Would process' : 'Processed'} ${filesChanged} files with ${totalChanges} total changes`);
|
|
}
|
|
|
|
main();
|