refactor(scripts): delete 20 one-off/legacy scripts
The scripts/ directory had accumulated 29 entries, half of which were one-off fixes that had already done their job and several of which were Supabase-era artifacts that don't apply now that the project uses direct Postgres via `pg`. Deleted (none referenced from package.json, CLAUDE.md, MEMORY.md, .gitea/workflows/deploy.yml, db/seeds/, or src/): Supabase-era one-offs (the project moved off Supabase JS/REST): - apply-admin-create-stop.js (RPC installer against supabase.co) - fix-archived-rls.js (RLS repair against supabase.co) - seed.sh (pure supabase REST seeding) - seed_tuxedo_tour.py (Python equivalent of the JS seed) Codemods / lint fixers that ran once: - fix-server-auth.js, fix-server-auth-ast.js - fix-button-has-type.js, fix-control-has-associated-label.js Versioned iteration history of one-off checks: - check-stop-fns.js, check-stop-fns2.js, check-stop-fns3.js - verify-stop-fns.js (only referenced by apply-admin-create-stop.js) Other one-offs: - cleanup-duplicate-stops.ts - create-admin-user.ts, seed-admin.ts (superseded by provision-admin.ts) - seed-tuxedo.ts (superseded by seed-tuxedo-2026.js) - import-woo-to-route.ts (WooCommerce import, no longer used) - upload-tuxedo-video.mjs, verify-email.ts, e2e-test.sh Also updated one stale comment in src/actions/admin/users.ts that referenced scripts/seed-admin.ts. Kept (9 scripts, all live-referenced): - migrate.js (package.json + deploy.yml + MEMORY.md) - db-reset.js (package.json: db:reset) - seed-tuxedo-2026.js (package.json: db:seed:tour) - import-tuxedo-stops.ts (db/seeds preferred path) - provision-admin.ts (CLAUDE.md production bootstrap) - preflight-check.js, postflight-check.js (.gitea/workflows/deploy.yml) - generate-pwa-icons.js, generate-pwa-screenshots.js (reproducible PWA build assets, referenced in design docs) Verified: tsc clean, 174/175 tests pass (same baseline), package.json scripts and deploy.yml all still resolve to kept scripts.
This commit is contained in:
@@ -1,223 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Uses TypeScript AST to find <input>/<select>/<textarea> JSX elements
|
||||
* missing aria-label/aria-labelledby and adds aria-label.
|
||||
* Idempotent and structurally safe.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ts = require('typescript');
|
||||
|
||||
function titleCase(s) {
|
||||
return s
|
||||
.replace(/[-_]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : ''))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function getJsxAttr(name, attrs) {
|
||||
for (const a of attrs.properties) {
|
||||
if (a.kind === ts.SyntaxKind.JsxAttribute && a.name && a.name.text === name) return a;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStringAttrValue(name, attrs) {
|
||||
const a = getJsxAttr(name, attrs);
|
||||
if (!a || !a.initializer) return undefined;
|
||||
if (a.initializer.kind === ts.SyntaxKind.StringLiteral) return a.initializer.text;
|
||||
if (a.initializer.kind === ts.SyntaxKind.JsxExpressionContainer && a.initializer.expression) {
|
||||
const e = a.initializer.expression;
|
||||
if (e.kind === ts.SyntaxKind.StringLiteral) return e.text;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function hasBooleanAttr(name, attrs) {
|
||||
return !!getJsxAttr(name, attrs);
|
||||
}
|
||||
|
||||
function getNameText(name) {
|
||||
if (!name) return null;
|
||||
if (name.kind === ts.SyntaxKind.Identifier) return name.text;
|
||||
if (name.kind === ts.SyntaxKind.PropertyAccessExpression) return name.name.text;
|
||||
return null;
|
||||
}
|
||||
|
||||
function inferLabelFromSiblingsOrPreceding(sourceFile, node) {
|
||||
const scanner = ts.createScanner(ts.ScriptTarget.Latest, /*skipTrivia*/ true, ts.LanguageVariant.JSX, sourceFile.text);
|
||||
// Walk text before the node to find preceding label or {label(...)}
|
||||
const start = node.getStart(sourceFile);
|
||||
const before = sourceFile.text.slice(Math.max(0, start - 800), start);
|
||||
// Look for label("...") in the most recent expression
|
||||
const labelCallMatch = before.match(/label\(\s*['"`]([^'"`]+)['"`]\s*\)/);
|
||||
if (labelCallMatch) return labelCallMatch[1];
|
||||
// Look for a label JSX element without closing
|
||||
const lastLabelOpen = [...before.matchAll(/<label\b[^>]*>(?:(?!<\/label>)[\s\S])*$/gi)];
|
||||
if (lastLabelOpen.length) {
|
||||
const last = lastLabelOpen[lastLabelOpen.length - 1];
|
||||
const afterOpen = before.slice(last.index + last[0].match(/^<label\b[^>]*>/i)[0].length);
|
||||
// If there's a </label> after, this label isn't enclosing
|
||||
if (!/<\/label>/i.test(afterOpen)) {
|
||||
const inner = afterOpen.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
if (inner) return inner.replace(/[*?]\s*$/, '').trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isInsideLabel(sourceFile, node) {
|
||||
let parent = node.parent;
|
||||
while (parent) {
|
||||
if (parent.kind === ts.SyntaxKind.JsxElement) {
|
||||
const opening = parent.openingElement;
|
||||
const tagName = getNameText(opening.tagName);
|
||||
if (tagName === 'label') return true;
|
||||
// Also check if it's inside a JsxFragment whose parent is a label
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
// Also check if we're directly inside a label's children
|
||||
let cur = node;
|
||||
while (cur.parent) {
|
||||
if (cur.parent.kind === ts.SyntaxKind.JsxElement && getNameText(cur.parent.openingElement.tagName) === 'label') {
|
||||
return true;
|
||||
}
|
||||
cur = cur.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function inferLabel(node, sourceFile) {
|
||||
const attrs = node.attributes;
|
||||
// Type attribute
|
||||
const typeAttr = getStringAttrValue('type', attrs);
|
||||
const placeholder = getStringAttrValue('placeholder', attrs);
|
||||
const nameAttr = getStringAttrValue('name', attrs);
|
||||
const idAttr = getStringAttrValue('id', attrs);
|
||||
const className = getStringAttrValue('className', attrs);
|
||||
const tagName = getNameText(node.tagName);
|
||||
if (className && /\bhidden\b/.test(className) && typeAttr === 'file') return 'File upload';
|
||||
if (placeholder) {
|
||||
const cleaned = placeholder.replace(/\b(e\.g\.?|i\.e\.?|min\.?)\b/gi, '').trim();
|
||||
return titleCase(cleaned || placeholder);
|
||||
}
|
||||
if (nameAttr) return titleCase(nameAttr.replace(/\[.*?\]/g, ''));
|
||||
if (idAttr) return titleCase(idAttr);
|
||||
const ctx = inferLabelFromSiblingsOrPreceding(sourceFile, node);
|
||||
if (ctx) return ctx;
|
||||
if (tagName === 'input' && typeAttr) return titleCase(typeAttr);
|
||||
return titleCase(tagName);
|
||||
}
|
||||
|
||||
function findTargets(sourceFile) {
|
||||
const out = [];
|
||||
function visit(node) {
|
||||
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
||||
const tagName = getNameText(node.tagName);
|
||||
if (['input', 'select', 'textarea'].includes(tagName)) {
|
||||
const attrs = node.attributes;
|
||||
const hasAriaLabel = hasBooleanAttr('aria-label', attrs) || getStringAttrValue('aria-label', attrs) !== undefined;
|
||||
const hasAriaLabelledBy = hasBooleanAttr('aria-labelledby', attrs) || getStringAttrValue('aria-labelledby', attrs) !== undefined;
|
||||
const typeAttr = getStringAttrValue('type', attrs);
|
||||
if (!hasAriaLabel && !hasAriaLabelledBy && typeAttr !== 'hidden') {
|
||||
if (!isInsideLabel(sourceFile, node)) {
|
||||
out.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(sourceFile);
|
||||
return out;
|
||||
}
|
||||
|
||||
function escapeForJsx(s) {
|
||||
return s.replace(/&/g, '&').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function processFile(filePath) {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
const sourceFile = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, ts.LanguageVariant.JSX);
|
||||
const targets = findTargets(sourceFile);
|
||||
if (targets.length === 0) return 0;
|
||||
|
||||
// Sort by position descending so insertions don't shift earlier offsets
|
||||
targets.sort((a, b) => b.getStart(sourceFile) - a.getStart(sourceFile));
|
||||
|
||||
let updated = text;
|
||||
let changes = 0;
|
||||
for (const node of targets) {
|
||||
const inferred = inferLabel(node, sourceFile);
|
||||
if (!inferred) continue;
|
||||
// Find position: right after the tag name
|
||||
const tagNameEnd = node.tagName.getEnd();
|
||||
const ariaStr = ` aria-label="${escapeForJsx(inferred)}"`;
|
||||
// Reconstruct from the position
|
||||
const head = updated.slice(0, tagNameEnd);
|
||||
const rest = updated.slice(tagNameEnd);
|
||||
updated = head + ariaStr + rest;
|
||||
changes++;
|
||||
}
|
||||
if (changes > 0) {
|
||||
fs.writeFileSync(filePath, updated);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const TARGET_DIRS = ['src/app', 'src/components', 'src/wholesale', 'src/landing'];
|
||||
const SKIP_PATTERNS = [
|
||||
/node_modules/,
|
||||
/\.next\//,
|
||||
/dist\//,
|
||||
/coverage\//,
|
||||
/\.test\.[jt]sx?$/,
|
||||
/\.spec\.[jt]sx?$/,
|
||||
];
|
||||
|
||||
function walk(dir, files = []) {
|
||||
if (!fs.existsSync(dir)) return files;
|
||||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, ent.name);
|
||||
if (SKIP_PATTERNS.some((rx) => rx.test(p))) continue;
|
||||
if (ent.isDirectory()) walk(p, files);
|
||||
else if (/\.(tsx|jsx)$/.test(ent.name)) files.push(p);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
let totalChanges = 0;
|
||||
let totalFiles = 0;
|
||||
const changedFiles = [];
|
||||
const errors = [];
|
||||
for (const t of TARGET_DIRS) {
|
||||
const dir = path.join(ROOT, t);
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
const files = walk(dir);
|
||||
for (const f of files) {
|
||||
try {
|
||||
const c = processFile(f);
|
||||
if (c > 0) {
|
||||
changedFiles.push([c, path.relative(ROOT, f)]);
|
||||
totalFiles++;
|
||||
totalChanges += c;
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push(`${f}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
changedFiles.sort((a, b) => b[0] - a[0]);
|
||||
for (const [c, f] of changedFiles) {
|
||||
console.log(` ${c.toString().padStart(4)} ${f}`);
|
||||
}
|
||||
if (errors.length) {
|
||||
console.error('\nErrors:');
|
||||
errors.forEach((e) => console.error(' ', e));
|
||||
}
|
||||
console.log(`\nTotal: ${totalChanges} aria-labels added across ${totalFiles} files`);
|
||||
Reference in New Issue
Block a user